整本书不厚,写的很好,解释的很清楚,虽然实际过程中不一定能注意到。每一章节的侧重点都有小结,回顾的时候可以先看小结,再探究有所遗忘的具体细节。
JavaScript阻塞性
将脚本放在底部
按照惯例, <script>
标签用来加载出现在<head>
中的外链JavaScript文件,挨着<link>
标签用来加载外部CSS文件或者其他页面信息。
理论上来说,把与样式和行为有关的脚本放在一起,并先加载他们,这样做有助于确保页面渲染和交互的正确性。
file1.js download –> file1.js execute –> file2.js download –> file2.js execute –> file3.js download –> file3.js execute –> style.css download –> style.css execute
因此推荐将所有的<script>
标签尽可能放到<body>
标签的底部(也就是</body>
之前),以尽量减少对整个页面下载的影响。
这是雅虎特别性能小组提出的优化JavaScript的首要规则:将脚本放在底部。
由于每个<script>
标签初始下载时都会阻塞页面渲染,所以减少页面包含的<script>
标签数量有助于改善这一情况。(下载单个100KB的文件将比下载4个25KB的文件更快)
文件合并的工具可通过离线的打包工具或者实时在线服务。
减少JavaScript文件大小并限制HTTP请求数仅仅是创建响应迅速的Web应用的第一步。
无阻塞脚本的秘诀在于,在页面加载完成后才加载JavaScript代码。
不是一个理想的跨浏览器解决方案。带有defer属性的
<script>
标签可以放置在文档的任何位置。随影的JavaScript文件将在页面解析到<script>
标签时开始下载,直到DOM加载完成(onload事件被触发前)才会执行。
1 | <script type="text/javascript" src="file1.js" defer></script> |
如果需要的话,你可以动态加载尽可能多的JavaScript文件到页面上,但一定要考虑清楚文件的加载顺序。
1 | // 下载操作串联以保证下载顺序 |
如果多个文件的下载顺序很重要,更好的做法是把它们按正确顺序合并成一个文件。
动态脚本加载凭借着它在跨浏览器兼容性和易用的优势,成为最通用的无阻塞加载解决方案。
1 | // firefox\opera\chrome\safari |
1 | // IE(封装版) |
另一种无阻塞加载脚本的方法是使用XMLHTTPRequest对象获取脚本并注入页面中。优点是你可以下载JavaScript代码但不立即执行,并且没有浏览器兼容性问题。缺点是JavaScript文件必须与所请求的页面处于相同的域。
1 | var xhr = new XMLHttpRequest(); |
向页面中添加大量JavaScript的推荐做法只需两步:先添加动态加载所需的代码,然后加载初始化页面所需的剩下的代码。
1 | // 放在`</body>`前,不会阻碍页面其他内容的显示 |
</body>
闭合标签之前,将所有的<script>
标签放到页面底部。<script>
标签的defer属性<script>
元素来下载并执行代码计算机科学中有一个经典问题是通过改变数据的存储位置来获得最佳的读写性能。
JavaScript有下面四种基本的数据存取位置。
字面量只代表自身,不存储在特定位置。JavaScript中的字面量有:字符串、数字、布尔值、对象、数组、函数、正则表达式,以及特殊的null和undefined值。
开发人员使用关键字var定义的数据存储单元。
存储在JavaScript数组对象内部,以数字作为索引。
存储在JavaScript对象内部,以字符串作为索引。
通常的建议是:如果在乎运行速度,那么尽量使用字面量和本地变量,减少数组项和成员对象的使用。
作用域概念是理解JavaScript的关键所在,不仅从性能角度,还包括从功能的角度。
每一个JavaScript函数都表示为一个对象,是Function对象的一个实例。Function对象和其他对象一样,拥有可以编程访问的属性,和一系列不能通过代码访问而仅供JavaScript引擎存取的内部属性。
其中一个内部属性是[[Scope]],包含一个函数北川兼得作用域中的对象的集合。这个集合被称为函数的作用域链。作用域链中的每一个可变对象都以键值对的形式存在。
例:
1 | function add(num1, num2) { |
创建了add函数对象,[[scope]] -> [0: {全局对象}],全剧对象:
{ this: window,
window: (object),
document: (object),
add: (function)
}
1 | // 执行 |
执行函数会创建一个称为执行上下文的内部对象。函数每次执行时对应的执行环境都是独一无二的,所以多次调用同一函数会创建多个执行环境。当函数执行完毕,执行环境就被销毁。
每个执行环境都有自己的作用域链,用于解析标识符。当执行环境被创建时,它的作用域链初始化为当前运行函数[[scope]]对象。这些值按照它们出现在函数中的顺序,被复制到执行环境的作用域链中,推到作用域链的顶端。当执行环境被销毁,活动对象也随之销毁。
(执行环境)var total = add(5, 10);的作用域链 -> [{活动对象}, {全局对象}] -> 活动对象:
{
this: window,
arguments: [5, 10],
num1: 5,
num2: 10,
sum: undefined
}
一个标识符在作用域链中所处位置越深,它的读写速度也就越慢,读写局部变量总是最快的,读写全局变量通常是很慢的,全局变量总存在于执行环境作用域链的最末端。一个好的经验法则是:如果某个跨作用域的值在函数中被引用一次以上,那么就把它存储到局部变量里。
有两个语句可以在执行时临时改变作用域链,with和try… catch…。
1 | with(document) { |
with会创建一个新的变量推入作用域链的首位,这意味着函数所有的局部变量现在处于第二个作用域链对象中,因此访问的代价更高了。最好避免使用with语句,可以使用存入局部变量中来提升性能。
跳转到Catch子句,会把异常对象推入一个变量对象并置于作用域的首位。在catch代码块内部,函数所有的局部变量将会放在第二个作用域链对象中。一旦catch子句执行完毕,作用域链就会返回到之前的状态。
1 | // 委托给错误处理函数,没有访问局部变量,作用域链的临时改变就不会影响代码性能 |
无论是with语句还是try-catch语句的catch子句,或是包含eval()的函数,都被认为是动态作用域。只在确实有必要时才推荐使用动态作用域。
闭包是JavaScript最强大的特性之一,它允许函数访问作用域之外的数据。闭包可能会导致性能问题。
例:
1 | function assignEvents() { |
为了让这个闭包能够方位id,必须创建一个特定的作用域链,闭包的[[scope]]属性包含了与执行环境作用域链相同的对象引用,因此会产生副作用。需要更多的内存开销。(函数的活动对象会随着执行环境一同销毁,但如果引用还存在于闭包的[[scope]]中,激活对象无法被销毁。)
assignEvents()执行 -> [{活动对象}, {全局对象}]
闭包被创建时,-> [{活动对象(assignEvents)}, {全局对象}]
闭包代码执行时, -> [{活动对象(闭包)}, {活动对象(assignEvents)}, {全局对象}]
闭包执行时,频繁访问跨作用句的标识符(assignEvents中的变量),会带来性能损失。
可以如先前提到的,讲常用的跨作用域变量存储在局部变量中,然后直接访问局部变量。
大部分JavaScript代码是以面向对象风格编写的,这会导致非常频繁的访问对象成员。对象成员包含属性和方法。当一个被命名的成员引用一个函数,该成员就被称为一个方法,引用了非函数类型的成员,就被称为属性。
为什么访问对象成员的速度比访问字面量或者变量要慢?
JavaScript中的对象是基于原型的。原型是其他对象的基础,它定义并实现了一个新创建的对象所必须包含的成员列表。这些对象实例也共享了原型对象的成员。对象通过一个内部属性绑定到它的原型。
对象可以有两种成员类型:实例成员和原型成员。实例成员直接存在于对象实例中,原型成员则从对象原型继承而来。
例:
1 | var book = { |
对象book中有两个实例成员,title和publisher,方法toString是原型成员.
book {‘proto’: {book原型}, title: ‘xxx’, publisher: ‘xxx’}.
book原型 {‘_proto’: null, hasOwnProperty: (function), …, toString: (function)}.
先从对象实例开始搜索,没有找到的话继续搜索其原型对象。
1 | var book = { |
对象的原型决定了实例的的类型。默认情况下,所有随想都是对象(Object)的实例,并继承了所有的基本方法。你可以定义并使用构造函数来创建另一种类型的原型。
1 | // 构造函数 |
对象在原型链中的位置越深,找到它也就越慢。搜索实例成员比从字面量或局部变量中读取数据代价更高,再加上便利原型链带来的开销,这让性能问题更加严重。
对象成员嵌套的越深,读取速度就会越慢。执行location.href总是比window.location.href要快。
通常在函数中如果要多次读取同一个对象属性,最佳做法是将属性值保存到局部变量中。
用脚本进行DOM操作的代价很昂贵,它是富Web应用中最常见的性能瓶颈。本章讨论一下三类问题:
文档对象模型是一个独立于语言的,用来操作XML和HTML文档的程序接口(API)。
浏览器中通常会把DOM和JavaScript独立实现。
两个相互独立的功能只要通过接口彼此连接,就会产生消耗。推荐的做法是尽可能减少访问DOM的次数。
访问DOM元素是有代价的,修改元素则更为昂贵,因为它会导致浏览器重新计算页面的几何变换。通常的经验法则是: 减少访问DOM的次数,把运算尽量留在ECMAScript这一端处理。(多于一次的话,缓存在局部变量中)
在大多数浏览器中,克隆节点(element.cloneNode)比创建节点(document.createElement)更有效率,但也不是很明显。
HTML集合
HTML集合是包含了DOM节点引用的类数组对象,以下方法和属性的返回值为HTML集合对象,这是个类似数组的列表,但并不是真正的数组(因为没有push()或slice()之类的方法),但提供了一个类似数组中的length属性,并且还能以数字索引的方式访问列表中的元素。
1
2
3
4
5
6
7
8 document.getElementsByName()
document.getElementsByClassName()
document.getElementsByTagName()
document.images
document.links
document.forms
document.forms[0].elements
HTML集合一直与文档保持着连接,每当你需要最新的信息时,都会重复执行查询的过程,哪怕只是获取集合里的元素个数(length)也是如此。这正是低效之源。
很多情况下,如果只需要遍历一个相对较小的HTML集合,那么缓存length就够了。但由于遍历数组比遍历集合快,因此如果先将几何元素拷贝到数组中,那么访问它的属性会更快。根据情况使用。
访问集合元素时使用局部变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// 较慢,每次读取全局document
function collectionGlobal() {
var coll = document.getElementByTagName('div'),
len = coll.length;
name = '';
for(var count = 0; count < len; count++) {
name = ducument.getElementByTagName('div')[count].nodeName;
name = ducument.getElementByTagName('div')[count].nodeType;
name = ducument.getElementByTagName('div')[count].tagName;
}
return name;
}
// 较快,缓存了一个集合的引用
function collectionGlobal() {
var coll = document.getElementByTagName('div'),
len = coll.length;
name = '';
for(var count = 0; count < len; count++) {
name = coll[count].nodeName;
name = coll[count].nodeType;
name = coll[count].tagName;
}
return name;
}
// 最慢,当前集合元素存储到一个变量
function collectionGlobal() {
var coll = document.getElementByTagName('div'),
len = coll.length;
name = '';
el = null;
for(var count = 0; count < len; count++) {
el = coll[count];
name = el.nodeName;
name = el.nodeType;
name = el.tagName;
}
return name;
}
获取DOM元素
childNodes、nextSibling
元素节点
大部分现代浏览器提供的API只返回元素节点(childNodes,firstChild和nextSibling并不区分元素节点和其他类型节点,例如注释和文本节点),实现的过滤效果要更高。
属性名 | 被替代的属性 |
---|---|
children | childNodes |
childElementCount | childNodes.length |
firstElementChild | firstChild |
lastElementChild | lastChild |
nextElementSibling | nextSibling |
previousElementSibling | previousSibling |
1 | var element = document.querySelector('.myClass'); |
elements的值包含一个引用列表,指向位于id=“menu”的元素之中的所有a元素。方法返回一个NodeList——包含着匹配节点的类数组对象。这个方法不会返回HTML集合,因此返回的节点不会对应实时的文档结构。避免了HTML集合引起的性能问题。
浏览器下载完页面中的所有组件——HTML标记、JavaScript、CSS、图片——之后会解析并生成两个内部数据结构
- DOM树——表示页面结构
- 渲染树——表示DOM节点如何显示
DOM树种的每一个需要显示的节点在渲染树中至少存在一个对应的节点(隐藏的DOM元素在渲染树中没有对应的节点)。渲染树中的节点被称为“boxes”,符合CSS模型的定义。一旦DOM和渲染树构建完成,浏览器就开始显示(绘制)页面元素。
重排——DOM的变化影响了元素的几何属性和位置,浏览器会使渲染树中受影响的部分失效,并重新构造渲染树。
重绘——完成重排后,浏览器会重新绘制受影响的部分到屏幕中。
最小化重绘和重排
1 | // 改变样式 |
缓存布局信息,例如用current把el.offsetLeft缓存下来。
代码的整体结构是影响运行速度的主要因素之一。
JS中有四中循环类型,for,while,do…while,for…in,由于for…in每次迭代操作会同时搜索实例或原型属性,所以会产生可以更多开销。除非你明确需要迭代一个属性数量未知的对象,否则应避免使用for…in循环。
提高循环的性能有以下两个方面:
1 | for(var i = 0; i < items.length; i++) {...} |
即使循环体重执行最快的代码,累计迭代上千次也会变慢下来,最广为人知的一种限制循环迭代次数的模式被称为“达夫设备”。如果迭代超过1000次,那么执行效率将明显提升。
1 | var i = items.length % 8; |
在条件数量较大(多与两个离散值)时使用switch,条件数量较少时使用if-else。
优化if-else
最小化到达正确分之前所需判断的条件数量,也就是说确保最可能出现的条件放在首位。
1 | // 多个至于需要测试时 |
当有大量离散值需要测试时,使用查找表会比if-else和switch从速度和可读性都好很多
1 | switch(value) { |
递归函数的潜在问题是终止条件不明确,或缺少终止条件会导致函数长时间运行,并使得用户界面处于假死状态。还可能遇到浏览器的“调用栈大小限制”。
JavaScript引擎支持的递归数量与JavaScript调用栈大小直接相关(IE例外)。
递归模式
1 | // 引起调用栈限制的两种递归模式 |
迭代
任何递归能实现的算法同样可以用迭代来实现,实现的要慢一些,但是可以避免栈溢出。
Memoization
Memoization正是一种避免重复工作的方法,它缓存前一个计算结果供后续计算使用,避免了重复工作。
1 | // 创建一个缓存对象, |
1 | // 手动更新 |
手工的更好,可以针对性的手工实现优化(缓存特定的参数的函数调用结果)。
高效处理字符串和正则表达式的各种技巧。
+、+=、array.join()、string.concat()。
这些操作符提供了连接字符串最简单的方法。
1 | str += "one" + "two"; |
大多数浏览器中,数组项合并比其他字符串连接方法更慢。
很灵活,但是比简单的使用 + 和 += 稍慢。
1 | str = String.prototype.concat.apply(str, array); |
编译
转化成一个原生代码程序。如果你把正则对象赋值给一个变量,可以避免重复执行这一步骤。
设置起始位置
它是字符串的起始位置,匹配失败的话后移一位。浏览器厂商有进行一些优化来跳过一些不必要的步骤。
匹配每个正则表达式字元
知道了开始位置之后,会逐个检查文本和正则表达式模式。
匹配成功或失败
回溯是影响正则表达式整体性能的其中一环,理解它的工作原理以及如何最少化的使用它可能是编写高效正则表达式的关键所在。
建议总是用包含特殊匹配的长字符串来测试你的正则表达式,为你的正则表达式构建一些近似但不能完全匹配的字符串,并将他们用在你的测试中。
只是搜索字面字符串时
trim方法
这一张的内容暂时遇到的情况比较上,虽然了解了正则表达式的回溯为什么会有性能的差异,但是不是很会运用正则表达式,等熟练运用了,再回过头看一下。
用于执行JavaScript和更新用户界面的进程通常被称为“浏览器UI线程”,UI线程的工作基于一个简单的队列系统,任务会被保存到队列中直到进程空闲,一旦空闲,队列中的下一个任务就被重新提取出来并执行。
// 点击按钮执行功能
ui更新 -> JS执行 -> ui更新
大多数浏览器在JavaScript运行时会停止把新任务加入UI线程的队列中,也就是说JavaScript任务必须尽快结束,以避免对用户体验造成不良影响。
浏览器限制了JavaScript任务的运行时间,以确保某些恶意代码不能通过永不停止的密集操作锁住用户的浏览器或计算机。这种限制分为两类:调用栈大小限制和长时间运行脚本限制。
单个JavaScript操作花费的总时间不应该超过100毫秒。
有一些复杂的JavaScript任务不能在100毫秒或更短时间内完成。停止执行JavaScript,使UI线程有机会更新,然后再继续执行JavaScript。
setTimeout() 只执行一次的定时器
setInterval() 一个周期性重复运行的定时器
1 | function greeting() { |
无论发生何种情况,创建一个定时器会造成UI线程暂停,如同它从一个任务切换到下一个任务。因此,定时器代码会重置所有相关的浏览器限制,包括长时间运行脚本定时器。此外,调用栈也在定时器的代码中重置为0.这一特性使得定时器称为长时间运行JavaScript代码理想的跨浏览器解决方案。
如果setTimeout()中的函数需要消耗的比定时器演示更长的运行时间,那么定时器代码中的延时几乎是不可见的。
JavaScript虽然是单线程的,但是浏览器可以是多个线程,所以会出现上面的情况。
JavaScript定时器延迟通常不太精准,相差大约几毫秒,所以定时器不可以用于测量实际时间。
建议将最小值设定为25毫秒以确保至少有15毫秒延迟(win系统中定时器)。
1 | var todo = items.concat(); // 克隆原数组 |
如果函数运行时间太长,可以拆分成一系列更小的步骤,把每个独立的方法放在定时器中调用。你可以将每个函数都放入一个数组,然后使用前一小节的数组处理模式。
前提是任务可以异步处理而不影响用户体验或造成相关代码错误。
最好不要让任何JavaScript代码持续运行50毫秒以上没这样做只是确保代码永远不会影响用户体验。
批量处理经历的延时比较少,总时间会短。
可以通过原生的Date对象来跟踪地阿妈的运行时间。
1 | // do-while后测比前测更合理,因为执行时数组中始终会包含至少一个条目 |
先前的代码使用了定时器序列,同一时间只有一个定时器存在,只有当这个定时器结束时才会新创建一个。通过这种方法使用定时器不会导致性能问题。当多个重复的定时器同时创建往往会出现性能问题。
Web Workers API 引入一个接口,能使代码运行且不占用浏览器UI线程的时间。作为HTML5最初的一部分,Web Workers API已经被分离出去成为独立的规范。Web Workers已经被FireFox3.5、 Chrome3和Safari4原生支持。
Web Workers给Web性能带来潜在的巨大性能提升,因为每个新的Worker都在自己的线程中运行代码。这意味着Worker运行代码不仅不会影响浏览器UI,也不会影响其他Worker中运行的代码。
每个Web Worker都有自己的全局运行环境,其功能只是JavaScript特性的一个子集。
1 | var worker = new Worker("code.js"); |
Worker与网页代码通过事件接口进行通信。网页代码可以通过postMessage()方法给Worker传递数据,Worker用onMessage()接受信息。
1 | var worker = new Worker("code.js"); |
1 | importScripts("file1.js", "file2.js"); |
任何超过100毫秒的处理过程,都应当考虑Worker方案是不是比基于定时器的方案更为合适。前提是浏览器支持Web Workers。
例:
1 | // 解析一个很大的JSON字符串 |
JavaScript和用户界面更新在同一个进程中进行,一次只能处理一件事情。
Ajax是高性能JavaScript的基础。
- 延迟下载体积较大的资源文件来使得页面加载更快
- 通过异步方式在客户端和服务器端之间传输数据
- 甚至可以用一个HTTP请求就获取整个页面的资源
选择合适的传输方式和最有效的数据格式,可以显著改善用户和网站的交互体验。
Ajax是一种与服务器通信而无需重载页面的方法,数据可以从服务器获取或发送给服务器。
有常用的五种技术:
XMLHttpRequest
是目前最常用的技术,它允许异步发送和接收数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var url = '/data.php';
var params = [
'id=934875',
'limit=20'
];
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState === 4) {
var responseHeaders = req.getAllResponseHeaders();
var data = req.responseText;
// ...do something
}
}
req.open('GET', url + '?' + parmas.join('&'), true) ;
req.setRequestHeader('X-Request-With', 'XMLHttpRequest');
req.send(null);
缺点:
使用XHR时,post和get的对比
动态脚本注入
能跨域,但只能GET(不需要实例化一个专用对象,所以有很多限制)
响应消息作为脚本标签的源码,它必须是可执行的JavaScript代码,所以速度非常的快(不用进行字符串处理),但引入外部来源的代码要小心。
1 | var scriptElement = document.createElement('script'); |
Multipart XHR
允许客户端只用一个HTTP请求就可以从服务端向客户端传送多个资源。它通过在服务端将资源(CSS、HTML片段、JavaScript片段或base64编码的图片)打包成一个由双方约定的字符串分割的长字符串并发送到客户端。
显著提升整体性能
XMLHttpRequest
1 | var url = '/data.php'; |
Beacons
这项技术非常类似动态脚本注入。
1 | var url = '/status_tracker.php'; |
考虑数据格式唯一需要比较的就是速度,一种可能下载更快,而另一种可能解析更快。
优势: 极佳的通用性、格式严格、且易于验证。
劣势: 非常的冗长,有效数据比例非常低(依赖大量的结构),解析非常消耗精力(要了解结构)。
一个更有效的方法是把每个值转化为<user>
标签的属性,文件尺寸变小,解析起来也更容易。(结构层次会变少)1
2
3
4
5
6
7
8
9<user id="2">
<username>bob</username>
<realname>Bob Jones</realname>
<email>bob@bobjones.com</email>
</user>
<!-- ↓ -->
<user id="2" username="bob" realname="Bob Jones" email="bob@bobjones.com" />
XPath在解析XML文档时比getElementsByTagName快许多,但它并未得到广泛支持。(所以必须使用DOM遍历方法编写降级的代码)
比起更先进的技术,不推荐使用XML格式
JSON是一种使用JavaScript对象和数组直接编写的轻量级且易于解析的数据格式。
当它被求值或分装在一个回调函数中时,JSON数据就是一段可执行的JavaScript代码,这意味着可以简单的使用eval()来解析(JSON数据是被当成字符串返回的!!!),但是不推荐,尽可能使用JSON.parse()方法解析字符串本身。
1 | // 会解析更快,但是可读性变差的改写 |
在使用动态脚本注入时,JSON数据被当成另一个JavaScript文件并作为原生代码执行。为了实现这一点,这些数据必须封装在一个回调函数里。这就是所谓的“JSON填充(JSON with padding)”或JSON-P。(JSON-P数据是当做原生的JavaScript!!!)
1 | // 回调包装的原因略微增大了文件尺寸,但与其解析性能的提升相比这点增加显得微不足道。 |
服务器处理好简单的HTML传回客户端,JavaScript可以很方便的通过innerHTML属性把它插入页面响应的位置。
作为一种数据格式,它既缓慢,又臃肿。
当你创建自定义格式时,最重要的决定之一就是采用哪种分隔符。
1 | 1:alice:Alice Smith:alice@alicesmith.com |
自定义格式可以很快速的下载,且易于解析,只需要简单地调用字符串split()并传入分隔符作为参数即可。
对于非常大的数据集,它是最快的格式,甚至在解析速度和平均加载时间上都能击败本地执行的JSON。当你需要在很短的时间内向客户端传送大量数据时可以考虑使用这种格式。
最快的Ajax请求就是没有请求。有两种主要的方法可避免发送不必要的请求:
在服务端,设置HTTP头信息以确保你的响应会被浏览器缓存
1 | 必须是GET方式发出请求,HTTP头信息中设置 |
在客户端,把获取到的信息存储在本地,从而避免再次请求
1 | var localCache = {}; |
第一种技术使用最简单而且好维护,第二种则给你最大的控制权。
直接操作XHR对象减少了函数开销,进一步提升了性能,但是放弃使用Ajax类库,可能会遇到兼容性问题。
高性能的Ajax包括以下方面:了解你项目的具体需求,选择正确的数据格式和与之匹配的传输技术。
JavaScript像其他很多脚本语言一样,允许你在程序中提取一个包含代码的字符串,然后动态执行它。有四中标准方法可以实现
1 | var num1 =5, |
当你在JavaScript代码中执行另一端JavaScript代码时,会导致双重求值的性能消耗。(先以正常的方式求值,然后在执行过程中对包含于字符串中的代码发起另一个求值运算)。双重求值比直接包含的代码执行速度慢许多。
1 | // 避免使用eval()\Function() |
使用对象和数组直接量是最快的方式,且运行的更快,还节省代码量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22var myObject = new Object();
myObject.name = "Nicholas";
myObject.count = 50;
myObject.flag = true;
myObject.pointer = null;
var myArray = new Array();
myArray[0] = "Nicholas";
myArray[1] = 50;
myArray[2] = true;
myArray[3] = null;
// ↓
var myObject = {
name = "Nicholas";
count = 50;
flag = true;
pointer = null;
}
var myArray = ["Nicholas", 50, true, pointer = null];
别做无关紧要的工作,别重复做已经完成的工作
信息在被使用前不会做任何操作
调用延迟加载函数时,第一次总会消耗更长的时间,因为它必须运行检测接着再调用另一个函数完成任务。但随后调用相同的函数会更快,因为不需要在执行检测逻辑。当一个函数在页面中不会立刻调用时,延迟加载是最好的选择。
在脚本家在期间提前检测,不会等到函数被调用。检测的操作依然只有一次,只是它在过程中来的更早。
条件预加载确保所有函数调用消耗的时间相同。其代价是需要在脚本加载时就检测,而不是加载后。预加载适用于一个函数马上就要被用到,并且在整个页面的生命周期中频繁出现的场合。
1 | // 对2取模 |
JavaScript的原生部分在你写代码前已经存在浏览器中了,并且都是用低级语言写的,诸如C++。特别是数学运算和DOM操作(querySelector())。
一个软件构建自动化工具
可以减少请求数
预处理你的JavaScript源文件并不会让应用变得更快,但它允许你做些其他事情,例如有条件的插入测试代码,来衡量你的应用程序的性能。
断言和和外的日志代码只出现在开发过程的DEBUG宏块中。这些语句并不会出现在最终产品中。
JavaScript压缩指的是吧JavaScript文件中所有与运行无关的部分进行剥离的过程。剥离的内容包括注释和不必要的空白字符。该过程通常可以将文件大小减半,促使文件更快被下载,并鼓励程序员编写更好更详细的行内文档。
开发高性能应用的一个普遍规则是,只要是能在构建时完成的工作,就不要留到运行时去做。
当应用升级时,你需要确保用户下载到最新的静态内容。这个问题可以通过把改动过的静态资源重命名解决。
内容分发网络(CDN)是在互联网上按地理位置分布计算机网络,他负责传递内容给终端用户。
在脚本运行期间执行各种函数和操作,找出需要优化的部分
检察图片、样式表和脚本的加载过程,以及它们对页面整体加载和渲染的影响。