JS开销
JS 开销在哪
资源大小相同的情况下,JS 开销更高
Bottom-Up :自下而上,可以看下里面具体做了哪些事情,耗时多久
Evaluate Script :解析耗时 101.6ms
The cost of JavaScript in 2019
对于一个网站而言,总共的网络加载过程中,压缩后 1.4 M 的 JS 在整个网络加载耗时中占 1/3
解决方案
减少主线程工作量:
Progressive Bootstrapping(渐进式启动):
- Navigation begins:第一个字节
- First Paint:白屏开始有内容绘制
- FCP: First Contentful Paint :导航启动
- FMP: First Meaningful Paint:页面内容是可见的
- Visually ready:页面基本绘制完成
- Time to Interactive:页面完全能进行交互
- Fully Loaded:结束这轮生命周期
配合 V8 进行优化代码
V8 编译原理
V8中Speculative Optimization简介
当 Chrome 或 Node 要执行一段 JS 代码时,首先会进行解析(Parse it),并将其翻译成一个抽象语法数(AST),之后把文本识别成字符,然后把重要信息提取出来,变成一些节点存储在一定的数据结构里(Interpreter)。最后把代码编成机器码之前,编译器会进行优化工作(Optimize Compiler),但是有时它自动优化工作并不一定合适(逆优化),所以我们需要在代码层面做的优化尽量满足它的优化条件,之后按照它的期望代码去写即可
perf_hooks 性能钩子
注释掉和没注释掉 add(num1, 's') 各执行一次观察 duration 持续时间。在执行函数时,发现参数类型发生变化,运行时不能用已经做过的优化逻辑了,就会把刚做的优化撤销,会造成一定的延时
const { performance, PerformanceObserver } = require('perf_hooks')
const add = (a, b) => a + b
const num1 = 1
const num2 = 2
performance.mark('start')
for (let i = 0; i < 10000000; i++) {
add(num1, num2)
}
for (let i = 0; i < 10000000; i++) {
add(num1, num2)
}
performance.mark('end')
const observer = new PerformanceObserver(list => {
console.log(list.getEntries()[0])
})
observer.observe({ entryTypes: ['measure'] })
performance.measure('测量', 'start', 'end')
如果想进一步了解 V8 做了什么优化,可以利用 Node 的两个参数(trace-opt 、trace-deopt )
node --trace-opt --trace-deopt de-opt.js
抽象语法数:
- 源码 -> 抽象语法数 -> 字节码 Bytecode -> 机器码
- 编译过程进行优化
- 运行时可能发生反优化
优化机制
-
脚本流 脚本正常情况要先进行下载再进行解析最后执行的过程,Chrome 在这里做了优化,在下载过程中可以同时进行解析就可以加快这个过程。当下载一个超过 30 KB 的脚本时,可以先对这 30 KB 内容进行解析,会单独开一个线程去给这段代码进行解析,等整个都下载完在完成时再进行解析合并,最后就可以执行,效率就大大提高了。这是流式处理的一个特点 -
字节码缓存 有些东西使用频率比较高,可以把它进行缓存,再次进行访问时就可以加快访问。源码被翻译成字节码之后,发现有一些不仅在当前页面有使用,在其他页面也会有使用的片段,就可以把这些片段对应的字节码缓存起来,在其他页面再次进行访问相同逻辑时,直接从缓存去取即可,不需要再进行翻译过程,效率就大大提高了 -
懒解析 对于函数而言,虽然声明了这个函数,但是不一定会马上用它,默认情况下会进行懒解析(先不去解析函数内部的逻辑,当使用时再去解析函数内部逻辑),效率就大大提高了
函数优化
lazy parsing 懒解析与 eager parsing 饥饿解析- 利用
Optimize.js 优化初次加载时间
懒解析与饥饿解析
export default () => {
const add = (a, b) => a*b
const num1 = 1
const num2 = 2
add(num1, num2)
}
class App extends React.Component {
constructor(props) {
super(props)
test()
}
}
module.exports = {
entry: {
app: './src/index.jsx',
test: './src/test.js',
},
output: {
path: `${__dirname}/build`,
filename: '[name].bundle.js',
},
}
对象优化
做这些优化的目的:迎合 V8 引擎进行解析,把你的代码进行优化。因为它也是用代码写的,所做的优化其实也是代码实现的规则,如果我们的代码迎合了这些规则,就可以帮你去做优化,代码效率可以得到提升
- 以相同顺序初始化对象成员,避免隐藏类的调整
- 实例化后避免添加新属性
- 尽量使用 Array 代替 array-like 对象
- 避免读取超过数组的长度
- 避免元素类型转换
以相同顺序初始化对象成员,避免隐藏类的调整
JS 是动态、弱类型语言,写的时候不会声明和强调它变量的类型,但是对于编辑器而言,实际上还是需要知道确定的类型,在解析时,它根据自己的推断,会给这些变量赋一个具体的类型,通常管这些类型叫隐藏类型(hidden class),之后所做的优化都是基于隐藏类型进行的
- 隐藏类型底层会以描述的数组进行存储,数组里会去强调所有属性声明的顺序,或者说索引,索引的位置
class RectArea {
constructor(l, w) {
this.l = l
this.w = w
}
}
const rect1 = new RectArea(3, 4)
const rect2 = new RectArea(5, 6)
const car = { color: 'red' }
car1.seats = 4
const car2 = { seats: 2 }
car2.color = 'blue'
实例化后避免添加新属性
const car = { color: 'red' }
car1.seats = 4
尽量使用 Array 代替 array-like 对象
array-like 对象:JS 里都有一个 arguments 对象,包含了函数参数变量的信息,本身是一个对象,但是可以通过索引去访问里面的属性,它还有 length 属性,像是一个数组,但又不是数组,不具备数组上面的方法,比如:forEach- V8 引擎会对数组能极大性能优化,目前有 21 种不同的元素类型,最好是把类数组转成数组再进行遍历,这样会比不去转成数组直接遍历效率高
Array.prototype.forEach.call(arrObj, (val, index) => {
console.log(`${index}:${value}`)
})
const arr = Array.prototype.slice.call(arrObj, 0)
arr.forEach((value, index) => {
console.log(`${index}:${value}`)
})
避免读取超过数组的长度
arr[3] -> undefined 会与数字进行比较(JS 里越界不一定会报错)- 如果在数组对象里找不到,会沿着原型链向上找,所以会进行额外的开销
- 越界比较会造成原型链额外的查找,性能相差 6 倍
function foo(arr) {
Array.prototype['3'] = 10000
for (let i = 0; i <= arr.length; i++) {
if (arr[i] > 1000) {
console.log(arr[i])
}
}
}
foo([10, 100, 1000])
避免元素类型转换
你可能不知道的V8数组优化
JavaScript 在 V8 中的元素种类及性能优化
const arr = [3, 2, 1]
arr.push(4.4)
arr.push('x')
arr[9] = 1
类型越具体,编辑器能做的优化就越多,如果越通用,能做的优化余地就越少
- 只能通过格子向下过度,一旦将单精度浮点数添加到 Smi 数组中,即使稍后用 Smi 覆盖浮点数,它也会被标记为 DOUBLE。类似地,一旦在数组中创建了一个洞,它将永久标记为有洞 HOLEY,即使稍微填充它也是如此
HTML 优化
- 减少 iframes 使用
- 压缩空白符
- 避免节点深层次嵌套
- 避免使用 table 布局
- 删除注释
- CSS 和 JavaScript 尽量使用外联
- 删除元素默认属性
减少 iframes 使用:
-
额外添加了文档,需要加载的过程,也会阻碍父文档的加载过程,也就是说如果它加载不完成,父文档本身的 onload 事件就不会触发,一直等着它。在 iframe 里创建的元素,比在父文档创建同样的元素,开销要高出很多 如果非得用 iframe,可以做个延时加载 <iframe id="iframe"></iframe>
<script>
document.getElementById('iframe').setAttribute('src', url)
</script>
压缩空白符:
- 编程时,为了方便阅读,会留空行和换行,最后打包要把空白符去掉
避免节点深层次嵌套:
- 嵌套越深消耗越高,节点越多最后生成 DOM 树占用内存会比较高
避免使用 table 布局:
- table 布局本身有很多问题,使用起来没有那么灵活,造成的开销比较大
删除元素默认属性:
- 本身默认那些值,没必要写出来,写出来就添加了额外的字符,造成了不必要的浪费
借助工具
CSS 优化
CSS 优化:
|