前端优化是一个永恒的话题,每个前端开发者都希望自己的页面能够快速加载,给用户良好的体验。但往往事与愿违。因此,本文从编码优化、构建优化、部署优化三方面入手进行web页面性能优化。
1. 编码优化
1.1. Css优化
1.1.1. 合理使用css选择器
CSS 查找样式表时是从右往左查询的,当遇到一个标签选择器如 span 时,会先遍历页面里所有的 span元素,然后先过滤掉祖先元素不是.name-and-status的元素,再过滤掉.name-and-status的祖先不是.readonly-instance-info的,依次像左查询,这个过程遍历了很多不会用到的标签,并且嵌套层级越多,匹配所要花费的时间代价也会更高。
不过现代浏览器在这一方面做了很多优化,不同选择器的性能差别并不明显,所以在使用选择器的时候注意以下几点:
-
保持简单,不要使用嵌套过多过于复杂的选择器,最好嵌套不超过三层以上,可以考虑使用类似BEM规范的方式进行css className的命名,有效避免更多的嵌套。
-
通配符和属性选择器效率最低,需要匹配的元素最多,尽量避免使用。
-
不要使用类选择器和ID选择器修饰元素标签,如h3#markdown-content,这样多此一举,还会降低效率。
-
不要为了追求速度而放弃可读性与可维护性。
1.1.2. 减少昂贵属性的使用
在浏览器绘制屏幕时,所有需要浏览器进行操作或计算的属性相对而言都需要花费更大的代价。当页面发生重绘时,它们会降低浏览器的渲染性能。所以在编写CSS时,我们应该尽量减少使用昂贵属性,如box-shadow/border-radius/filter/透明度/:nth-child等。
并不是说不要使用这些属性,而是当有两种方案选择时,可以优先选择性能消耗少的属性。
1.1.3. 使用BEM规范
BEM(块、元素、修饰符)是一种前端开发中的命名规范,旨在提高代码的可读性、可维护性和可协作性。BEM是由Yandex团队提出的一种前端CSS命名方法论,它使用简洁的命名规则来描述组件及其状态,使代码易于理解、易于维护、易于扩展和重构。
BEM命名规则:
- 块(Block):块是一个独立的实体,代表一个可重用的组件或模块。
块的类名应该使用单词或短语,并使用连字符(-)作为分隔符。例如:.header、.menu。
- 元素(Element):元素是块的组成部分,不能独立存在。
元素的类名应该使用双下划线(__)作为分隔符,连接到块的类名后面。例如:.menu__item、.header__logo。
- 修饰符(Modifier):修饰符用于描述块或元素的不同状态或变体,用来更改外观或行为。
修饰符的类名应该使用双连字符(–)作为分隔符,连接到块或元素的类名后面。例如:.menu__item–active、.header__logo–small。
BEM 命名法的好处:
BEM的关键是,可以获得更多的描述和更加清晰的结构,从其名字可以知道某个标记的含义。于是,通过查看 HTML 代码中的 class 属性,就能知道元素之间的关联。
常规的命名法示例:
<div class="article">
<div class="body">
<button class="button-primary"></button>
<button class="button-success"></button>
</div>
</div>
这种写法从 DOM 结构和类命名上可以了解每个元素的意义,但无法明确其真实的层级关系。在 css 定义时,也必须依靠层级选择器来限定约束作用域,以避免跨组件的样式污染。
使用了 BEM 命名方法的示例:
<div class="article">
<div class="article__body">
<div class="tag"></div>
<button class="article__button--primary"></button>
<button class="article__button--success"></button>
</div>
</div>
通过 BEM 命名方式,模块层级关系简单清晰,而且 css 书写上也不必作过多的层级选择。
1.1.4. tailwindcss第三方插件
Tailwind CSS是一个功能类优先的CSS框架,它集成了flex、text-center这样的类,可以实现开发者无需离开HTML页面,同时也无需编写复杂的 CSS 选择器或嵌套规则,很大程度减少了css代码体积,也免于起名的烦恼。不过不得不承认,原子化的CSS方案,都会或多或少的增大HTML的文件体积,因为我们总归是将样式代码从CSS文件里挪用到了HTML的标签里,不过搭配gzip压缩的话,会起到事半功倍的效果,因为gzip的压缩效率与文档的字符重复程度呈正相关,换句话来说,也就是当文档里的重复字符越多时,文档压缩后的体积就会越小,而tailwindcss书写的class,绝大多数都是重复的类名,这样大大减少了类名所造成的体积影响。
1.1.5. 确保文本在网页字体加载期间保持可见状态
利用 font-display 这项 CSS 功能,确保文本在网页字体加载期间始终对用户可见。
1.2. Js书写优化
1.2.1. 条件判断语句优化
- 离散值多分支逻辑优化
if(value === 0) {
// to do
} else if(value === 1) {
// to do
} else if(value === 2) {
// to do
} else {
// to do
}
匹配的条件仅为一两个离散值时,if-else的处理时间一般会很快,而当匹配的条件为多个枚举值时,可优先选择switch语句,switch语句会比if-else有更高性能表现。
优化后:
switch(value) {
case 0:
// to do
break;
case 1:
case 2:
// to do
break;
default:
// to do
}
switch可以清晰的表明判断条件和返回值之间的对应关系,同时switch还能使不同的条件指向相同的出来逻辑,具有更好的代码可读性。
虽然switch语句在逻辑上确实比else if语句简单,但是代码本身也有点多,还可以利用对象属性查询的方式达到条件判断的目的,代码优化如下:
let enums = {
'A': handleA,
'B': handleB,
'C': handleC,
'D': handleD,
'E': handleE
}
function action(val){
let handleType = enums\[val]
handleType()
}
实际上还可以通过Map来进一步优化枚举值的条件判断的代码。
对比基于对象属性的映射方式,Map具有许多优点:
-
对象的键只能是字符串或符号,而Map的键可以是任何类型的值。
-
使用Map size属性可以轻松获取Map的键/值对的数量,而对象的键/值对的数量只能手动确定。
-
具有极快的查找速度。 代码优化如下:
let enums = new Map(\[
\['A', handleA],
\['B', handleB],
\['C', handleC],
\['D', handleD],
\['E', handleE]
])
function action(val){
let handleType = enums.get(val)
handleType()
}
条件判断的书写建议:
-
当匹配的条件仅有一两个离散值判断时,使用if-else语句。
-
当匹配的条件超过一两个但少于十个离散值时,使用switch语句。
-
当匹配条件超过十个离散值时,使用对象索引或者Map数据结构的查找方式。
- 排非策略
比如用户登录场景,如果用户名和密码输入框为空,那么我们就提示用户”用户名和密码不能为空”;如果有值,就执行登录的操作。 优化前
if (user && password) {
// 逻辑处理
} else {
throw('用户名和密码不能为空!')
}
优化后
if (!user || !password) return throw('用户名和密码不能为空!')
// 逻辑处理
表单提交时,需要提前排除那些提交不规范的内容,通常情况下,表单提交遇到不符合我们要求大于我们提交成功的情形,排非策略是个很不错的选择。
- if-else优化
在if-else语句的使用过程中,如果可以预估条件被匹配到的频率,按照频率的高低顺序来排列if-else语句,可以让匹配频率高的条件更快执行。如果匹配频率高的条件放在了最后的else中,那么之前的所有条件都需要经历一遍,从而会增加