一、JS开销和如何缩短解析时间
1. JS开销
在字体,html,图片这些资源当中,最为昂贵的是JavaScript。它后面还有解析和编译的过程。最后才是执行.
以Reddit.com完整网站为例JS处理的时间大概需要占网页加载的耗时三分之一。而已由于JS加载阻塞,就算用户能够看到我们的页面,也没办法交互。
2. 如何缩短解析时间
① Code splitting 代码查分,按需加载。
当前访问路径需要哪些资源就加载哪些资源,不需要的我们给它延迟,访问的时候再去加载。达到减少加载js的目的
② Tree shaking 代码减重
举例来说,我们只是引用了loadsh里面的一个函数,就可以把这一个函数打包到bundle文件中。
③ 减少主线程的工作量
-
避免长任务:任务时间越长,占据的阻塞越久
-
避免超过1kb的行间脚本:写行间脚本可能是为了加快首屏的渲染。剩下的再通过web文件进行加载。对于行间脚本,浏览器不能进行优化。
-
使用rAF和rIC进行时间调度
3.我们应该关注的三个问题:happening、useful、usable
二、V8编译原理
1. V8是什么?
V8是Google开发的开源js引擎,目前用在chrome浏览器和node.js中,用于执行js代码。V8是js虚拟机中的一种,js虚拟机就是把js编程语言翻译成机器语言。市面上比较流行的js引擎,SpiderMonkey,v8,JS core等。
2. V8执行JS的过程
简述:源码=》抽象语法树=》字节码Bytecode=》机器码
注意:编译过程会进行优化,运行时可能会发生反优化
参考文章:V8引擎学习-V8怎么执行JS的
3. V8优化机制
- 脚本流
检查超过30kb的脚本,就认为已经足够大,就会先进行解析(单独开一个线程进行解析)。等这个所有的都加载完成之后再进行解析时就可以提高效率,因为只要把之前解析的合并下就可以了。
- 字节码缓存
经常使用的变量进行缓存
- 懒解析
主要针对于函数,先不去解析函数内部的逻辑,当真正用到函数时才去解析。
三、函数优化
1. lazy parseing 懒解析 vs eager parseing 饥饿解析
懒解析的好处,如果不需要解析,那就不用在“堆”里面分配内存,不用为它创建一个语法树。可以提高我们加载js的一个整体的效率。
但是现实中,我们有时候还是需要我们的函数立即去执行的。假如我们先进行懒解析,然后发现需要立即执行,还需要一个饥饿解析,这样反而效率减半。所以我们要告诉解析器某个函数需要被立即执行,需要进行饥饿解析即可。
只需添加一个括号即可,如果添加的括号会在压缩的时候被去掉,可以利用Optimize把括号找回来。
旧:const add = (a, b) => a+b;
新:const add = ((a, b) => a+b);
2. 利用Optimize.js优化初次加载时间
优化一个JavaScript文件更快的初始执行和解析,通过包装所有立即执行函数或类执行函数在括号中。
参考github:optimize-js
四、对象优化
我们要尽量去迎合V8引擎的解析机制,便于对代码的优化。
- 以相同顺序初始化对象成员,避免隐藏类的调整(隐藏类时有顺序的,顺序不对应的话会生成新的隐藏类型,无法复用之前生成的隐藏类型)
class RectArea { // HC0
constructor(l, w) {
this.l = l; // HC1
this.w = w; // HC2
}
}
const rect1 = new RectArea(3,4); // 创建了隐藏类HC0, HC1, HC2
const rect2 = new RectArea(5,6); // 相同的对象结构,可复用之前的所有隐藏类
const car1 = {color: 'red'}; // HC0
car1.seats = 4; // HC1
const car2 = {seats: 2}; // 没有可复用的隐藏类,创建HC2
car2.color = 'blue'; // 没有可复用的隐藏类,创建HC3
- 实例化后避免添加新属性
const car1 = {color: 'red'}; // In-objece 属性
const.seats = 4; // Normal/Fast 属性,存储property store里,需要通过描述数组间接查找。
- 尽量使用Array代替array-like对象,体现出转换的代价比优化的影响要小
Array.prototype.forEach.call(arrObj, (value, index) => { // 不如在真实数组上效率高
console.log(`${ index }: ${ value }`);
});
const arr = Array.prototype.slice.call(arrObj, 0); // 转换的代价比影响优化小
arr.forEach((value, index) => {
console.log(`${ index }: ${ value }`);
});
- 避免读取超过数组的长度
function foo(array) {
for (let i = 0; i <= array.length; i++) { // 越界比较
if(array[i] > 1000) { // 1.沿原型链的查找 2.造成undefined与数进行比较
console.log(array[i]); // 业务上无效、出错
}
}
}
- 避免元素类型转换
const array = [3, 2, 1]; // PACKED_SMI_ELEMENTS
array.push(4.4); // PACKED_DOUBLE_ELEMENTS
参考文章:你可能不知道的V8数组优化
五、HTML优化
- 减少ifames 的使用
- 压缩空白符
- 避免节点深层次的嵌套
- 避免使用table布局
- 删除注释
- 语义化标签
- CSS和JavaScript尽量外链,JS放在下面,别阻塞DOM加载
- 删除元素默认属性
Webpack集成了这样的功能。(可以利用html-minifile工具)
六、CSS对性能的影响
通过Devtool工具观察瀑布图中的Recalculate Style,它会计算Render树(渲染树),然后从根节点开始进行页面渲染,将CSS附加到DOM上的过程,即为样式计算所开销的时间。
- 降低CSS的阻塞
① 加载层面:对首屏用的CSS样式到的尽早加载,用不到的推迟加载,这样可以把影响降低到最低。
② 文件大小层面:尽量减小文件体积。
- 利用GPU进行完成动画,既使用复合图层。
- 使用contain属性(contain:layout)。
告诉浏览器,我和外面的没关系,只对我里面的元素进行更改,不用进行回流,布局的重新计算。
4. font-display 属性
在页面上先展示文字,减轻文字闪动的问题