《高性能javascript》读书笔记

153 阅读6分钟
  1. 买了一本动物书,将里面有用的东西提炼出来,读完本篇可以当做读过本书了吧。

  2. script标签,每个文件必须等到前一个文件下载并执行完成才会开始下载。

<script src="a.js"></script>
<script src="b.js"></script>
  1. 减少页面中外部脚本文件的数量将会改善性能
<script src="bundle.js"></script>
  1. defer属性指明本元素所含的脚本不会修改DOM,因此代码能安全地延迟执行
<script defer src="bundle.js"></script>
  1. defer属性的script标签,在window.onload执行之前被调用
  2. 动态创建DOM加载js:无论何时下载,文件的下载和执行过程不会阻塞页面其他进程
let script = document.createElement('script');
script.src = 'bundle.js';
document.getElementByTagName('head')[0].appendChild(script)
  1. 添加大量js代码的推荐做法:先加载动态加载所需的代码,在动态加载其他代码
  2. 尽量使用字面量和局部变量,减少数组和对象的使用
  3. 执行函数时会创建执行环境,当函数执行完毕,执行环境就被销毁
  4. 如果某个跨作用域的值在函数中被引用一次以上,建议使用局部变量引用
let global = 1;
function test(){
    console.log(global + global)
}
// 改进为
let global = 1;
function test(){
    let num = global;
    console.log(num + num)
}
  1. 使用hasOwnProperty来判断对象是否包含特定的实例成员,in操作符来判断是否包含特定的属性
let obj = {
    num: 1
}
obj.hasOwnProprtey("num") // true
"toString" in obj // true
  1. 对象成员嵌套得越深,读取石速度就会越慢
  2. 如果要多次读取对象的一个属性,最佳做法是将属性保存到局部变量中
let obj = {
    num: 1
}
console.log(obj.num + obj.num)
// 改进为
let obj = {
    num: 1
}
let num = obj.num
console.log(num + num)
  1. 12中提到的方法,在保存对象的方法时需要小心this的指向变为window
let obj = {
    name: 'obj',
    fn: function() {
        console.log(this.name)
    }
}
obj.fn() // obj
let fn = obj.fn; // fn内的this指向变成window了
fn() // undefined
  1. 减少访问DOM的次数,把运算尽量留在ECMAScript这一端处理
  2. getElementsByTagName()等DOM方法返回一个集合,该集合是实时刷新的,读取DOM集合的速度比读取数组的速度慢。
let list = document.getElementsByTagName('div');
console.log(list.length) // 假设此时输出5
// 此处添加一个div
console.log(list.length) // 此时输出6
  1. 当需要多次访问一个DOM的成员,最好使用一个局部变量保存
  2. 使用childNodes,nextSibling来遍历DOM元素
  3. querySelectorAll方法返回的NodeList不会实时刷新
  4. 当页面布局和几何属性改变时,DOM就会重排
let div = documemt.getElementsByTagName('div')[0]
div.width = 0; // 此时重排
  1. 浏览器会优化重排过程,但是代码读取布局信息时会导致强行重排
offsetTop
scrollTop
clientTop
getComputedStyle
  1. 减少重排次数:一次性修改多个属性
let div = documemt.getElementsByTagName('div')[0]
div.width = '100px'
div.height = '200px'
// 优化为
div.style.cssText = 'width:100px;height:200px';
// 为防止覆盖
div.style.cssText += 'width:100px;height:200px';
  1. 当需要多次操作DOM对象时,使元素脱离文档流可以减少重绘和重排 方法一:设置display为none,改了后再设置为visible; 方法二:使用文档片段创建一个DOM子树,修改后替换原节点; 方法三:复制节点出来后,处理复制节点,再用复制节点代替原节点
  2. 使用事件委托同时处理多个DOM的事件
  3. for-in循环比其他循环慢
  4. 通过倒序和减少属性查找可以提高性能
for(let i = item.length; i--; ){
    process(item[i])
}
  1. 达夫设备:一次循环里操作多个元素
let arr = [];
for (let i = 0; i < 30; i++) {
    arr.push(i)
}
let i = arr.length % 8;
let len = arr.length - 1;
while (i--) {
    console.log(arr[len--])
}
i = Math.floor(arr.length / 8);
while (i--) {
    console.log(arr[len--]);
    console.log(arr[len--]);
    console.log(arr[len--]);
    console.log(arr[len--]);
    console.log(arr[len--]);
    console.log(arr[len--]);
    console.log(arr[len--]);
    console.log(arr[len--]);
}
  1. 基于循环的迭代比基于函数的迭代快8倍
arr.forEach(item => {}) // 基于函数的迭代
  1. if-else语句应该按概率大小排序,概率最大的放最前
  2. 将if-else改成一系列嵌套得is-else,使用二分法查找
if(value===0){
} else if(value === 1){
} else if(value === 2){
} else if(value === 3){
} else if(value === 4){
} else if(value === 5){
}
// 改进为
if(value < 3){
    if(value === 0){
    } else if(value === 1){
    } else if(value === 2){
    }
} else {
    if(value === 3){
    } else if(value === 4){
    } else if(value === 5){
    }
}
  1. 使用数组或对象来构建查找表比if-else或switch快很多
  2. 把递归算法改成迭代实现可以避免栈溢出
  3. 缓存函数结果:对同样的参数只执行一次计算
function sum(num1, num2) {
    if (!sum.cache) {
        sum.cache = {}
    }
    let key = `${num1}+${num2}`
    if (!sum.cache[key]) {
        console.log('calc')
        sum.cache[key] = num1 + num2
    }
    
    return sum.cache[key]
}

console.log(sum(1, 2))
console.log(sum(1, 2))
console.log(sum(1, 2))
  1. regular expression => regexp
  2. str += 'one' + 'two'; 一、在内存中创建一个临时字符串 二、将'onetwo'赋值给临时字符串 三、str与临时字符串连接 四、结果赋值给str str = str + 'one' + 'two' 可以避免创建临时字符串
  3. 需要在100ms内响应用户的操作
  4. 当脚本执行时,UI不随用户交互而更新。用户连续点击多次按钮,只能看到一次按钮被点击,却执行多次点击响应事件
  5. 定时器的时间参数表示被添加到任务队列的时间,而不是执行的时间
  6. windows系统中定时器分辨率为15ms,定时为10ms时可能会被转为0或15ms
  7. 当执行很多逻辑时,可以用定时器让出UI更新的时间
let arr = [1, 2, 3, 4, 5, 6, 7, 8]
function test() {
    setTimeout(() => {
        console.log(arr.shift())
        if (arr.length) {
            test()
        }
    }, 25)
}
test()
  1. 39的改进:在一定时间内尽可能多的执行逻辑
let arr = [1, 2, 3, 4, 5, 6, 7, 8]

function test() {
    setTimeout(() => {
        let start = +new Date()
        do {
            console.log(arr.shift())
            // cosole.log操作耗时太短,所以1次就执行完了
        } while (arr.length && (+new Date() - start) < 50)

        if (arr.length) {
            test()
        }
    }, 25)
}
test()
  1. Worker步进不会影响浏览器,也不会影响其他Worker
// 主进程的代码
let worker = new Worker('code.js');
worker.onmessage = function(event){ // 处理worker发过来的消息
    console.log(event.data)
}
worket.postMessage('呼叫worker') // 向worker发送消息

// code.js worker里面的代码
self.onmessage = function(event){ // 处理主进程发过来的消息
    if(event.data === '呼叫worker'){
        self.postMessage('收到收到')
    }
}
  1. 只获取数据的请求应使用GET,浏览器会缓存请求结果,有助于提高性能
  2. 一般GET请求只发送一个数据包,POST发送两个数据包(头信息,正文)
  3. JSONP:定义一个处理响应数据的函数work,加载一个外部js,外部js调用work,传进响应数据
  4. 性能:数组化的JSON > 简化JSON > 普通JSON
// 普通JSON
[{
    name: 'a',
    value: 1
}, {
    name: 'b',
    value: 2
}]

// 简化JSON
[{
    n: 'a',
    v: 1
}, {
    n: 'b',
    v: 2
}]

// 数组JSON
[['a', 1], ['b', 2]]
  1. 延迟加载模式:在第一次调用时修改原函数,避免再次判断。
  2. 判断奇偶:位运算性能更好
i % 2 
// 优化为
i & 1