您的位置:首页 > Web前端 > JavaScript

《高性能JS》学习笔记

2017-04-27 18:26 225 查看

脚本阻塞

一些知识

无论是对于内联的JS还是外部文件,页面的渲染和交互都会阻塞,等待JS的下载和执行。

现在多数浏览器都能对多个JS文件进行并行下载,但是会阻塞其他类型资源的下载。必须等待JS完全下载并执行完后才能进行其他资源的下载。

如果将内嵌脚本放到
link
样式文件之后,脚本为了在执行的时候获取精准的样式信息,会等待样式表下载完成,这样造成了页面阻塞。因此,不要把内嵌脚本放到
link
标签之后。

defer和async脚本

这两者的共同点都是可以和其他资源并行下载,下载的过程中不会产生阻塞。不同点是执行的时机。
defer
属性需要等页面完成,即触发
load
事件后再执行,并且是按照出现在页面中的顺序来执行的;而
async
是下载完就执行,顺序不能保证。

动态脚本

即采用
createElement('script')
方式来动态生成一个脚本节点,当脚本节点插入到文档树中即开始下载执行。该方式的一个特点是下载和执行都不会阻塞页面其他进程。

但是有个问题是脚本的执行顺序,只有Firefox和Opera能按照你指定的动态节点的顺序执行,而其他浏览器都是下载完就立即执行。如果脚本间存在依赖,那么就会出现问题。可以监听脚本下载完的事件,在回调函数中指定下一个要下载的文件操作。

标识符搜索对性能的影响

在JS中有全部和函数执行环境这两种。每个函数创建后都有一个内部属性
[scope]
,该属性指向作用域链。这个属性是开发人员无法访问的,是供JS引擎进行存取的。

作用域链中包含了一个个指向变量对象的引用。变量对象用来承载每一个执行环境中可访问到的变量。

当函数创建的时候,作用域链中包含了包含它的函数和全局环境的变量对象。当函数调用的时候,创建了一个执行上下文(即执行环境),自己的变量对象会被
push
到作用域链的顶端,此时成为活动对象。此时活动对象中的形参、
arguments
对象等会被赋予相应的值,但此时函数内部并没有执行具体的语句,因此除了形参的其他用
var
声明的变量的值是
undefined
的。

问题:内部声明的函数有实际的值了?

由于标识符是沿着作用域链进行搜索,因此读写局部变量是最快的,全局变量最慢。

Tips: 如果跨作用域的变量在函数中被引用一次以上,就将其存储到局部变量中。

with
catch
都会临时将传入的对象添加到作用域链的顶端,当块中的代码执行完后,该对象就从作用域链中删除,块所处的作用域仍然属于块所处的函数(JS只有函数作用域和全局作用域),在块中声明的变量仍然是函数的局部变量。只不过用
with
catch
让标识符的搜索从传入的对象开始。

with
比较简单,看一个
catch
的例子:

(function(){
e="default";
try{
throw "test";
}catch(e){
var e,x=123;
console.log(e); //test
console.log(delete e); //false
e=456;
console.log(e); //456
};
console.log(x); //123
console.log(e); //default
console.log(window.e); //undefined
})();


下面的评论解释得更好:

1.首先谨记js只有函数作用域,语句块只是函数的一部分,try-catch也不例外。所以catch中的内容和外边function是在一个作用域中的,也因此,因为catch中有var e;所以function里的e也变成了局部变量。

2.catch确实会对作用域链做一个小动作,这个小动作就是把catch(e)中的e单独放入一个作用域,然后把这个作用域加入作用域链的最前端。所以catch花括号中的var e是在function的作用域中进行,而e=456时的这个e实际上是catch(e)中的e。这里之所以让你迷惑是因为你犯了个大错:javascript里delete不能用来删除变量,只能用来删除属性。你可以在delete e那一行之后再console.log(e)一下,你会发现还是输出test,这个变量没有被删除。

原博客地址:try-catch语句的“伪块作用域”

闭包

闭包的性能影响主要在于跨作用域的标识符访问。另外也比较消耗内存。不过可以将跨作用的变量存成局部变量。

对象属性访问

对象的属性嵌套层次越深、或者在原型链的嵌套层次越深,访问速度越慢。虽然有些浏览器作出了优化,但如果需要在一个方法内频繁访问对象的某个属性,最好将其存成局部变量。但该方式不能用在对象方法上,因为对象方法内部需要使用
this
来判断执行环境,如果将对象方法存成一个局部变量,则
this
会指向
window


操作DOM对性能的影响

DOM是独立于脚本语言的。浏览器多DOM(渲染)的实现和JS(引擎)的实现是分开的。JS想要操作DOM(访问、修改)通过接口调用,因此会带来调用消耗。尽可能减少DOM 的操作,把运算留在ECMAScript这端。

使用
arr.push('string')
,最后一次性插入某个元素中
parent.innerHTML = arr.join('')
与多次使用
createElement
appendChild
来分别插入,在现代浏览器中并没有太大性能差别。

诸如
document.getElementsByClassName('')
以及
document.forms
之类的HTML集合,是实时的,即当文档发生变化时,这些集合也自动更新。因此在获取集合
length
属性等操作时,都要重新执行查询(集合更新),会比常规数组慢很多。

可以将集合引用和集合中的元素存成局部变量。

操作DOM时尽量使用效率高的API,诸如
dom.children
dom.firstElementChild
,而不使用
dom.childNodes
dom.firstChild
,因为前者是返回元素节点,而后者返回包括文本在内的节点。

此外,
document.querySelectorAll()
接收CSS选择器为参数,会比使用
document.getElementsByTagName()
等选择器效率高。同时,前者是返回静态列表,和HTML文档没有关联,而后者返回动态的HTML集合。

算法流程

如果分支条件多的话用
switch
语句更快。优化
if-else
的原则就是减少到达最优分支前的判断,即将最可能出现的分支放到前面。

字符串

大多数浏览器中,直接的字符串拼接是优于数组方法的。只有IE7以前的数组方法才能避免字符串连接中的内存重复分配和拷贝带来的性能劣势。

字符串连接中的
str = str + ...
也是优于
str += ...
的。因为前者浏览器是直接为
str
开辟了大的内存空间,直接将后面的内容追加。而后者会先将等号右边的存成临时字符串,之后与
str
合并,再赋值给
str


UI线程

即浏览器开了一个单线程用于执行JS代码和更新用户界面,在进行其中一项时,另一项停滞。任务被添加到任务队列中,等UI线程空闲时就从中取出任务执行。在JS代码执行时,多数浏览器会停止将新任务加入队列。因此JS代码要尽快执行完。

浏览器一般会对JS代码最长执行时间做出限制或者限制调用栈,如果超过了就会终止脚本的执行。

注意
setTimeout
setInterval
都是间隔指定的时间后将回调函数推入任务队列中,当主线程中的代码执行完毕后,会依次取出任务队列中的回调函数来执行。

定时器

如果循环执行太久,同时不需要一定是同步,数据不用顺序处理,可以将循环分解到定时器中处理。函数也可以拆分成多个独立的函数,像数组那样设置成定时器来执行。为了避免将数组或独立函数分得细碎,可在一次延时中执行操作多个数组元素或函数。

为了不让用户觉察到阻塞,最好将JS代码的执行时间控制在50ms内。

tips:
+new Date()
可以将
Date
对象转换成数字。

Web Workers

此外,新版的浏览器支持
Web Workers
,它不占用通常运行JS的UI线程,而是单开了一个线程,因此可以用于运行需要耗费大量时间的JS代码。
Web Workers
提供了一些API用于和主线程上的JS代码通信。

Ajax

数据格式

JSON数据实质内容信息的比例越高越好,并注意组织数据的结构,这样下载和解析都会更快。JSON数据要解析成原生的JS对象才能在JS代码中使用,可以用
JSON.parse
eval
来解析,推荐使用前者。

解析是需要花费时间的。如果使用动态插入脚本的方式,即JSONP的方式,那个传入的JSON参数会被当做原生的JS代码,无需解析,速度会非常快。

也可以自定义数据格式,让数据只包含必要的数据,用分隔符来链接,然后使用
split
来解析,
split
是最快的字符串操作方式之一。

避免不必要的请求

服务端设置响应头要求缓存(需用
GET
请求方式),或者在客户端将数据缓存到一个对象中(不适合跨页面跨会话,且易被修改)。

留意下multipart XHR,可以减少请求数目,将多个文件合并以字符串形式放到一次响应中,客户端来解析。

采用了流传输数据,监听
readyState
为3的状态,在该状态下,使用轮训检测
xhr.responseText
可以分段实时处理响应数据。

其他

能用引擎提供的原生JS就用原生的,特别是数学运算和DOM操作。这些原生方法是用C++编写的,运行速度会比自己写的JS快很多。

querySelector() 和 querySelectorAll() 是基于JS的CSS查询时间的10%

问题:不太明白基于JS的CSS查询是啥意思
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  js 高性能 前端 优化