前言
这段时间把《高性能JavaScript》书籍读完,受益良多。在读书的过程把重点精简地提炼并结合自己的经验记录下来。也希望看完这篇文章能够给对JavaScript多了解一点点。
目录
- 加载和执行
- 数据存取
- DOM编程
- 算法和流程控制
- 快速响应的用户界面
- Ajax
- 编程实战
- 构建并部署高性能的JavaScript应用
1. 加载和执行
了解: 当浏览器在执行JavaScript代码时,是不能够同时做其他事情。因为,绝大多数的浏览器使用单一线程来处理UI和JS。简单地说,
1.1 脚本的位置
<html>
<head>
<script 1></script>
<script 2></script>
<script 3></script>
</head>
<body>
<div></div>
</body>
</html>
这样的代码存在十分严重的性能问题,整个解析的过程会卡顿在<script 1,2,3>的下载和执行的过程。而页面内容迟迟不能呈现在用户面前。
记住: 浏览器在解析n之前不会渲染页面的任何部分,此时的表现为空白。所以 < script > 放在顶部的做法非常不可取。
js文件的下载执行流程::
那么该如何去改进?
!!!并行下载js (后来的IE8、FireFox3.5、Safari4、Chrome2都允许并行下载JS文件了,也就是说你现在接触到的大多浏览器的JS文件的下载都是并行的)
也就是:::
JS文件是能够并行下载,但是页面还是会阻塞其他资源下载(如图片)并且会等待所有的JS文件下载完成并执行完成。
因此强烈推荐将所有的< script >标签尽可能的防盗标签的底部,已尽量减少对整个页面的影响。
1.2 组织脚本
考虑到Http请求会有额外的开销,所以script标签的个数不能过多。这个时候就要考虑将js文件进行合并已减少数量了。不过现在的很多打包工具都有这样的功能,所以不用太过关注这个问题。
1.3无阻塞脚本
减少JS文件的大小及数量是优化的第一步(毕竟效果有限,因为js总会越来越多,越来越大)所以要考虑无阻塞的加载脚本方式。
而无阻塞脚本的秘诀在于:页面加载完成后再就在JS代码(即在window.load()方法出发后再下载)
1.4延迟的脚本
Defer属性:
HTML4为< script >标签定义了defer扩展属性,该属性指明的JS文件不会修改DOM。但一开始只有IE4、FireFox3.5+支持;不过后来已经被所有的主流浏览器所支持。后来的H5中还添加了async扩展属性。
区别
defer | async |
---|---|
并行下载JS | 并行下载JS |
等待页面完成后执行(但是在load()方法调用之前) | 下载完成后执行 |
1.5动态脚本
< script >标签跟其他的元素一样,都能通过DOM操作。
let scriptaEle = document.createElement('script');
scriptEle.type = 'text/script';
scriptEle.src = 'file1.js';
document.getElementByTagName('head')[0].appendChild(scriptEle);
这个技术的重点在于:无论何时启动下载,文件的下载和执行的过程不会阻塞页面的其他过程。
温馨提示:
- 新创建的JS添加到< head >中会更好,因为在< body >中IE中可能会抛出“操作已终止”的信息
- 下载的JS通常会立即执行,除了opera和firefox会等待此前所有的动态脚本节点执行完毕)
有时该JS会被其他的JS调用其中的方法,所以有时需要监听JS下载的状态。
- firefox、opera、chrome、safari3以上 | 完成时会触发load()函数
- IE为< script >提供readystate属性,但并不是其中的所有状态都会执行(通常loaded/complete出现一种就可以)
所有状态 | Value |
---|---|
uninitialized | 初始 |
loading | 开始下载 |
loaded | 下载完成 |
interactive | 完成但未可用 |
complete | 已准备就绪 |
动态脚本凭借其在跨浏览其兼容性和易用的优势,成为最通用的无阻加载解决方案
记住哈,想要优化JS的下载就采用动态脚本技术!!!
1.6 XMLHttpRequest脚本注入
**了解:**另一种无阻塞加载脚本方式:使用XMLHttpRequest技术获取脚本并注入页面(也就是用XHR网络请求JS然后注入到页面中)
例如:
let xhr = new XMLHttpRequest();
xhr.open('get','file.js',true);
xhr.onReadyStateChange = function () {
if(xhr.readyState === 4) {\
if(xhr.state >= 200 && xhr.status < 300 || ==304) {
//请求JS成功后,创建script标签,将JS内容赋给script标签;然后嵌入页面
let script = document.createElement('script');
script.type = 'text/javascript';
script.text = xhr.responseText;
document.body.appendChild(script);
}
}
}
// 发起请求
xhr.send();
优点:
- JS不会立即执行,可以放到你准备好的时候再执行都可以(灵活控制JS的执行时间)
- 所有主浏览器都支持
1.7 推荐的无阻塞模式
向页面中添加大量JS的推荐做法:
第一步:先添加动态加载所有的代码
第二部:再加载剩余JS代码
有一些如LazyLoad的类库可以协助我们快速的使用无阻塞加载JS
2. 数据存取
了解: 计算机科学中有一个经典的问题:通过改变数据的存取位置来获得最佳的读写性能。数据的存取位置关系到代码执行过程中的检索速度。
js中有四种基本的数据存取位置:
字面量
: 字面量只代表本身,不存储在特定位置。
: 有:字符串、数字、布尔、数组、函数、正则表达式、nullull、undefined
本地变量
: 开发人员使用var、let等定义的数据存储单元
数组元素
: 存储在JS数组对象内部,以数字为索引
对象成员
: 存储在JS对象内部,以字符串为索引
2.1 管理作用域
**了解:**作用域是理解JS的关键,所以要重点的搞明白这一部分,从性能和功能的角度去思考。
- 确定哪些变量能被访问
- 确定this
- 关系到性能
需要了解以上问题的原理!
2.2 作用域链和标志符解析
**了解:**JS函数是Function对象,和普通对象一样拥有
-
可编程访问的属性
-
不可通过代码访问的内部属性(而这其中有着非常重要的 [scope] )
函数执行时创建一个为执行环境的内部对象。函数每次执行的都会创建独一无二的执行环境。当函数执行完毕,执行环境就会被销毁。
每个执行环境都有自己的作用域链,用于解析标志符,在函数执行的过程中,每遇到一个变量,都会经历标志符解析的过程以决定从哪里获取存储数据。从顶层的“活动对象”作用域开始便利作用域链,知道找到为止。
正式这个搜索过程,影响性能2.2 标志符解析的性能
一个标志符所在的位置越深,他的读写速度越慢;所以全局变量访问速度越慢(因为他总是在作用域链的末端)
(这也是链式结构的特点)
综上述:应该尽量多的访问局部变量。
经验法则:如果某个跨作用域的值在函数中被引用一次以上,则把他存储到局部变量2.3 改变作用域链
有两个语句可以在执行时,临时改变作用域链。
第一个:with
- 类似功能通常用来避免书写重复代码。
- 给对象的所有属性创建了一个变量。
- 并且把该对象添加到作用域链顶,访问该属性速度变快,但是访问其他的变慢
- 一般来说如果多次访问document,可以吧document付给局部变量即可大大提升性能
- with应该尽量避免使用
· 第二个:try…catch
-
当进入catch时,会将异常对象推入作用域链的顶部
2.4 动态作用域
with、try…catch、eval()都被认为是动态作用域链,动态作用域只存在于代码执行过程中,因此无法通过静态分析。
2.5 闭包、作用域和内存
闭包:js最强的特性之一,函数访问局部作用域之外的数据,使用闭包可能会导致性能问题,因为闭包函数阻碍了函数被正常回收,因为闭包有自己的作用域链,并且指向跟函数的作用域链一样。
当闭包代码执行时,会创建一个执行环境。
闭包代码中访问的属性的位置不在第一层,这就是使用闭包最需关注的性能点;在频繁访问跨作用域的标志符时,每次都有性能损失。(闭包同时关系到内存和执行速度)2.6 对象成员
访问成员比访问字面量或变量要慢,为了理解其中的原因,有必要先了解JS的对象本质。
2.7 原型
js的对象基于原型,他定义并实现了一个新创建的对象,所必须包含的成员列表。
对象通过一个内部属性帮到他的原型,在firefox、safari、chrome浏览器中,这个属性_proto_对开发者可见,而其他浏览器确不允许脚本访问此属性。小结
- 在JS中,数据存储的位置对代码整体性能产生重大的影响。
- 访问字面量和局部变量速度最快,相反,访问数组元素和对象成员相对较慢
- 由于局部变量在链顶位置,所以更快
- 全局变量在链尾位置,所以更慢
- 减少嵌套对象
- 通常来说,把常用的对象成员,数组元素,跨域变量保存到局部变量中可改变JS的性能
- 多用局部变量
不知不觉写到这里已经挺长的了,所以决定留到下一篇文章吧。
- 加载和执行
- 数据存取
- DOM编程
- 算法和流程控制
- 快速响应的用户界面
- Ajax
- 编程实战
- 构建并部署高性能的JavaScript应用