个经常被忽视的网站性能瓶颈发生在处理级联样式表和随后在网页的文档对象模型中应用 CSS 选择器。为了加快网页的渲染速度并改善最终用户体验,请考虑以下五个 CSS 性能优化技巧。
1. 使用内联样式
如果给定样式仅用于单个特定页面元素(例如图像滑块或轮播)并且该组件仅用于站点中的少数页面,请使用内联样式而不是通用加载的 CSS 文件。这不仅会减少外部样式表的大小,还会减少在不使用该组件的页面上发生的 CSS 选择器评估的数量。
2. 使用特定的样式
不要将样式应用于通用选择器、后代选择器和顶级 HTML 元素。这样做会触发许多布尔评估。相反,通过选择更细粒度的元素(例如单个类样式)来优化 CSS 性能。
3. 使用 WebComponents 优化 CSS
WebComponents 是自包含的,减少了对共享 CSS 和 JavaScript 的需求,WebComponents 框架是一种相对较新的基于标准的方法,用于创建可重用组件,其中 JavaScript 和样式是自包含和隔离的。当你将组件所需的样式保留在全局共享的 CSS 文件之外时,你不会看到对站点的其他区域的性能影响,因为未使用的样式应用程序会不必要地消耗时钟周期。
4. 拆分你的 CSS 文件
如果你的许多 CSS 文件是针对特定浏览器或设备定制的,请将这些样式分解为多个 CSS 文件。在运行时,仅加载必要的那些。
这个 CSS 性能优化技巧将需要一些客户端 JavaScript 或服务器端处理来完成,但对用户的好处将值得付出额外的努力。
例如,如果移动和桌面呈现显着不同,这可能会在每种设备类型上将文档对象模型 (DOM) 评估的数量减少 50%。
5. 减小 DOM 的大小
用于拆分 CSS 文件的技巧也可用于减小 DOM 的大小。
另一个 CSS 性能优化技巧类似于将 CSS 文件拆分为多个特定于设备的文件。相反,你还可以缩小网页本身的大小,减少 HTML 中 DOM 元素的数量,从而加快客户端渲染速度。
但是,处理 CSS 的问题不仅在于需要应用于页面的样式数量,还在于实际页面上的 DOM 元素数量。许多采用响应式设计的现代网页会在每个页面请求上加载 HTML 源代码,以实现移动和桌面体验。相反,使用客户端 JavaScript 甚至服务器端技术来确保不会将多余的 HTML 加载到永远不会使用它的页面上。这可以显着减少 DOM 大小以及页面需要经过的样式表评估次数。
样式表是现代网站开发人员最好的朋友,但它们通常会带来隐藏的性能成本。遵循这五个 CSS 性能优化建议,减少 CSS 选择器成为网站性能瓶颈的可能性。想往前端发展的小伙伴建议参加Web前端培训来学习前端技术,有系统规范的课程,有经验丰富的专业讲师面授指导教学,能在短时间内学有所成。
了解更多
文已经过原作者 EthicalAds 授权翻译。
作为开发人员,我们一直在寻找让我们的代码更快更好的方法。但在此之前,编写高性能代码需要做三件事:
记住这一点
任何傻瓜都可以编写计算机可以理解的代码,优秀的程序员编写人类可以理解的代码。- 丁·福勒
我们来看看如何使 JavaScript代码运行得更快。
延迟算法将计算延迟到需要执行时才执行,然后生成结果。
const someFn = () => {
doSomeOperation()
return () => {
doExpensiveOperation()
}
}
const t = someArray.filter((x) => checkSomeCondition(x)).map((x) => someFn(x))
// 现在,如果有需要在执行
t.map((x) => t())
最快的代码是未执行的代码,所以尽量延迟执行。
JavaScript 使用原型继承,JS 中所有对象都是Object的实例。
MDN说:
尝试访问对象的属性时,不仅会在对象上搜索该属性,还会在对象的原型,原型的原型等上搜索该属性,直到找到匹配属性名或原型链的末端。
对于每个属性,JavaScript引擎都必须遍历整个对象链,直到找到匹配项。如果使用不当,这会占用大量资源,并影响应用程序的性能。
所以不要这样:
const name = userResponse.data.user.firstname + userResponse.data.user.lastname
而是这样做:
const user = userResponse.data.user
const name = user.firstname + user.lastname
使用临时变量来保存链接的属性,而不是遍历访问整条链。
在上述情况下,userResponse可能不是对象,如果是对象,它的属性 user 也可能不是对象。所以,在获取值时要进行检查:
let name = ''
if (userResponse) {
const data = userResponse.data
if (data && data.user) {
const user = data.user
if (user.firstname) {
name += user.firstname
}
if (user.lastname) {
name += user.firstname
}
}
}
这太啰嗦了。代码越多,bug 就越明显。我们能把它缩小吗?当然,可以使用 JS 中可选的链接、解构赋值来优化它。
const user = userResponse?.data?.user
const { firstname = '', lastname = ''} = user
const name = firstname + lastname
是不是很灵活地,简短?不要使用这个要注意,Babel 会按照以下方式进行转换:
'use strict'
var _userResponse, _userResponse$data
var user =
(_userResponse = userResponse) === null || _userResponse === void 0
? void 0
: (_userResponse$data = _userResponse.data) === null ||
_userResponse$data === void 0
? void 0
: _userResponse$data.user
var _user$firstname = user.firstname,
firstname = _user$firstname === void 0 ? '' : _user$firstname,
_user$lastname = user.lastname,
lastname = _user$lastname === void 0 ? '' : _user$lastname
var name = firstname + lastname
当使用转译时,确保你选择了一个更适合你的用例的。
数字很奇怪,ECMAScript将数字标准化为64位浮点值,也称为双精度浮点或Float64表示形式。
如果 JS 引擎以Float64表示形式存储数字,则将导致巨大的性能低下。JS 引擎对数字进行抽象,使其行为与Float64完全匹配。与float64运算相比,JS 引擎执行整数运算的速度要快得多。
有时,我们认为像下面这样写法可读比较好:
const maxWidth = '1000'
const minWidth = '100'
const margin = '10'
getWidth = () => ({
maxWidth: maxWidth - margin * 2,
minWidth: minWidth - margin * 2,
})
如果getWidth函数被多次调用,那么每次调用它时都会计算它的值。上面的计算并不是什么大问题,因此我们不会注意到任何性能影响。
但是总的来说,运行时的求值的数量越少,性能就越好。
// maxWidth - (margin * 2)
const maxWidth = '980'
// minWidth - (margin * 2)
const minWidth = '80'
const margin = '10'
getWidth = () => ({
maxWidth,
minWidth,
})
如果要检查多个条件时,可以使用Map代替 switch/if-else条件。在Map中查找元素的性能比对switch和if-else条件快得多。
switch (day) {
case 'monday':
return 'workday'
case 'tuesday':
return 'workday'
case 'wednesday':
return 'workday'
case 'thursday':
return 'workday'
case 'friday':
return 'workday'
case 'saturday':
return 'funday'
case 'sunday':
return 'funday'
}
// or this
if (
day === 'monday' ||
day === 'tuesday' ||
day === 'wednesday' ||
day === 'thursday' ||
day === 'friday'
)
return 'workday'
else return 'funday'
上面可以使用 Map 来代替
const m = new Map([
['monday','workday'],
['tuesday', 'workday'],
['wednesday', 'workday'],
['thursday', 'workday'],
['friday', 'workday'],
['saturday', 'funday'],
['sunday', 'funday']
];
return m.get(day);
在 React组件中,这种写法还是很常见的。
export default function UserList(props) {
const { users } = props
if (users.length) {
return <UserList />
}
return <EmptyUserList />
}
在这里,我们在没有用户时渲染<EmptyUserList />否则渲染<UserList />。有大部分人认为,我们首先处理所有空的的情况,然后再处理有数据的情况。对于任何读过它的人来说都更清楚,而且效率更高。也就是说,以下代码比上一个代码更有效。
export default function UserList(props) {
const { users } = props
if (!users.length) {
return <EmptyUserList />
}
// some resource intensive operation
return <UserList />
}
当然 users.length 一直有值的话,就使用第一种情况。
JavaScript是解释型和编译型语言。为了产生更有效的二进制文件,编译器需要类型信息。但是,作为一种动态类型化的语言会使编译器难以进行。
编译器在编译热代码(多次执行的代码)时进行一些假设并优化代码。编译器花费一些时间来生成此优化的代码。当这些假设失败时,编译器必须丢弃优化的代码,并退回到解释的执行方式。这是耗时且昂贵的。
作者:EthicalAds 译者:前端小智 来源: sendilkumarn
原文:https://sendilkumarn.com/blog/javascript-optimization/
在前端开发中,我们往往会把性能问题当做编码过程的一个重要考虑因素,如何写出高质量代码是我们每个程序员追求的高境界。
今天这篇文章我们一起来看看如何写出一些高效的Javascript代码,提高执行效率吧。
Javascript
在Javascript中提供了with关键字,它可以将代码执行的作用域设定到一个特定的作用域中。
使用with语句可以减少代码量,例如以下的一些与location有关的执行语句。
不使用with
当使用with语句后,以上的代码可以优化成以下的方式。
使用with
我们可以看出,在代码编写上,如果使用with语句,代码块中就会包含一个独立的作用域,根据作用域链的思想,每次执行会优先寻找该作用域下的变量与方法。
使用with语句的一个最大问题是:Javascript引擎无法优化with代码块。
拿上面那段代码来说,Javascript引擎无法判断with代码块内部的search是局部变量还是location对象的一个属性,因此只要Javascript引擎遇到with关键字,就会放弃优化其中的代码块,造成性能下降。
因此我们得出的结论是:不要因为节省代码量而使用with关键字。
虽然不推荐使用全局变量,但是全局变量也有它的好处,最主要是写法简单,所有信息可以直接从window寻找。
但是其弊端也是很明显的,总结如下。
由于作用域链的存在,代码执行会优先从当前作用域寻找变量,如果引用全局变量,总是需要寻找到window对象才可以找到,非常耗时。
全局作用域中定义的变量会一直存在于内存中,会导致占用的内存越来越大,影响性能。
变量污染,当定义的全局变量越来越多,可能自己定义的变量名已经记不清了,后面的变量覆盖前面的值很可能发生,造成变量冲突。
下面我们通过一个简单的例子来验证使用全局变量会造成性能下降。
例子的主要功能是,执行一个for循环,每次拼接一个简单的字符,通过console.time()和console.timeEnd()方法测试执行的时间。
执行代码
我们将上述代码复制到Chrome控制台中执行,为了证实结果不是偶然的,我们执行三次,得到的结果如下所示。
结果1
结果2
结果3
通过上述的结果,可以看出在不使用全局变量的情况下,执行时间要远远低于使用全局变量的。
因此得出的结论是:在万不得已的情况下,不要使用全局变量。
在拼接字符串时,我们总是会习惯性的使用'+'运算符拼接多个字符串。
累计使用'+'
这种写法的可读性比较好,而且能够减少很多的代码量,在写法上非常常见。
但是如果考虑到性能问题,这种写法却不是最好的,这是为什么呢?
因为使用连续的'+'运算符,每使用一次都会生成一个临时变量去存储字符串,直到最后一次才会将结果赋值给变量a。
比如上述的代码,会生成一个临时变量保存字符串'xy',然后拼接到变量a的后面,最后才赋值给a。
而如果不使用累加形式拼接字符串,就不会出现这样的情况。
我们将上面的代码改写成以下形式。
改写形式
这种写法在执行时不会生成临时变量,而是将每次更新的值直接赋给变量a,这样的话性能会有所提升。
因此结论是:代码不复杂的情况下,尽量减少连续使用'+'连接符。
使用过Javascript定时器的同学都知道,setTimeout和setInterval方法可以传入函数,也可以传入字符串。
但是我们推荐传入函数的方式,如果传入字符串的话会影响性能。这是因为当你传入字符串的时候,它会被传进构造函数,并且重新调用另一个函数,影响执行过程。
如果传入函数的话,虽然在延迟一定的时间后才会执行,但是会被Javascript引擎优化,从而带来性能的提升。
例如以下是传入字符串的写法。
传如字符串
可以改写为以下传入函数的写法。
传入函数
Javascript中提升性能的方法还有很多,文章中只是列举了在编写代码过程中可以注意的问题。后面我还会整理与DOM操作有关的性能优化的文章,敬请关注~
*请认真填写需求信息,我们会在24小时内与您取得联系。