《高性能JavaScript》读书笔记(一)

第一章 加载和运行

JavaScript拥有阻塞的特征,当JavaScript运行时,其他的事情不能被浏览器处理。大多数浏览器使用单进程处理UI更新和JavaScript运行等多个任务,而同一时间只能有一个任务被执行。

<script>标签一旦出现,就会使整个页面因脚本解析、运行而出现等待。因为脚本可能在运行过程中修改页面内容。比如使用document.write()方法。

浏览器在遇到<body>标签之前,不会渲染页面的任何部分。将脚本放在页面的顶端,将导致一个可以察觉的延迟,通常表现为:页面打开时,首先显示为一副空白的页面,而此时用户不能进行阅读和操作。

IE8,Firefox 3.5,Safari 4和Chrome 2允许并行下载JavaScript文件,但JavaScript的下载仍然要阻塞其他资源的下载,例如图片等。

因此,推荐将所有的<script>标签放在尽可能接近<body>标签底部的位置,尽量减少对整个页面下载的影响。

此外,限制页面的<script>标签的数量也可以改善性能。每当页面解析碰到一个<script>标签时,紧接着有一段时间用于代码执行。最小化这些延迟时间可以改善页面的整体性能。每个HTTP请求都会产生额外的性能负担,下载一个100KB的文件比下载四个25KB的文件要快,所以减少引用外部脚本文件的数量,就可以减少性能损失。

可以使用打包工具或者使用CDN(内容分发网络)来实现:
例如,下面的URL包含两个文件(YUI,联合句柄)

http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js
此URL请求两个文件,这两个文件在服务器是分离的,但当服务器收到此URL请求时,两个文件会被合并在一起返回给客户端,从而减少了<script>标签

注意:

保持 JavaScript 文件短小,并限制 HTTP 请求的数量,只是创建反应迅速的网页应用的第一步。
一个应用程序所包含的功能越多,所需要的 JavaScript 代码就越大,保持源码短小并不总是一种选择。尽管下载一个大 JavaScript 文件只产生一次 HTTP 请求,却会锁定浏览器一大段时间。为避开这种情况,你需要向页面中逐步添加 JavaScript,某种程度上说不会阻塞浏览器。

非阻塞脚本
非阻塞脚本的秘密在于,等页面完成加载之后,再加载JavaScript代码。
实现方法:
1.延迟脚本

1
<script src="file.js" defer></script>

这个defer属性指明元素中所包含的脚本不打算修改DOM,因此代码可以稍后执行(DOM加载完后执行)。
(仅适用于IE4+和Firefox 3.5+)

2.动态脚本加载(最常用的模式)

1
2
3
4
var script = document.createElement ("script");
script.type = "text/javascript";
script.src = "file1.js";
document.getElementsByTagName("head")[0].appendChild(script);

file1.js当元素<script>添加到页面之后立刻开始下载。此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。
跨浏览器实现动态加载js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function loadScript(url, callback){
    var script = document.createElement ("script")
    script.type = "text/javascript";
    if (script.readyState){ //IE浏览器
        script.onreadystatechange = function(){
            if (script.readyState == "loaded" || script.readyState == "complete"){
                script.onreadystatechange = null;
                callback();    //js文件接收完成时触发的回调函数
            }
        };
    } else { //其他浏览器
        script.onload = function(){
            callback(); //js文件接收完成时触发的回调函数
        };
    }
    script.src = url;
    document.getElementsByTagName("head")[0].appendChild(script);
}

使用方法如下:

1
2
3
loadScript("file1.js"function(){
    alert("File is loaded!");
});

在大多数浏览器中,不会保证文件加载的顺序,只有Firefox和Opera能够保证脚本按照你指定的顺序执行。
可以采用串联的方式,保证下载次序:

1
2
3
4
5
6
7
loadScript("file1.js"function(){
    loadScript("file2.js"function(){
        loadScript("file3.js"function(){
        alert("All files are loaded!");
        });
    });
});

3.XHR脚本注入
使用XHR对象将脚本注入到页面中,继而实现非阻塞脚本。此技术首先创建一个XHR对象,然后下载JavaScript文件,接着用一个动态<script>元素将JavaScript代码注入页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var xhr = new XMLHttpRequest();
xhr.open("get","file1.js",true);
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){
        //2**表示有效的回应,304表示一个缓存响应
        if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.text = xhr.responseText;    // 创建内联代码的&lt;script&gt;元素
            document.body.appendChild(script);
        }
    }
}
xhr.send(null);

这种方法的优点是:
可以下载不立即执行的JavaScript代码;
兼容性好,同样的代码在所有现代浏览器中都不会引发异常。

限制:JavaScript文件必须与页面放置在同一个域内,不能从CDN下载。(所以大型网页不采用这方法)

推荐的非阻塞模式
推荐的向页面加载大量JavaScript的方法分为两个步骤:

第一步,包含动态加载的JavaScript所需的代码,然后加载页面初始化所需的除JavaScript之外的部分。这部分代码尽量小,它下载和运行非常迅速,不会对页面造成很大干扰。当初始代码准备好后,用它来加载其余的JavaScript代码。
另外,也可以直接将加载其余JavaScript代码的函数嵌入在页面中,这样可以避免另一HTTP请求。
典型示例是YUI3,核心设计理念是:用一个很小的初始代码,下载其余的功能代码。

非阻塞 JavaScript 加载库
使用LazyLoad实现加载其他的功能代码(http://github.com/rgrove/lazyload/)
可以下载多个JavaScript文件,并保证它们在所有浏览器上都能够按照正确的顺序执行。

总结
几种减少JavaScript对性能的影响的方法:
1)将所有<script>标签放置在页面的底部,紧靠body关闭标签</body>上方;
2)将脚本成组打包。页面<script>标签越少,页面加载速度就越快,响应也更加迅速。

几种使用非阻塞方式下载JavaScript的方法:
1)为<script>标签添加defer属性(仅适用于IE和Firefox3.5+)
2)动态创建<script>元素,用它下载并执行代码
3)用XHR对象下载代码,并注入到页面中

第二章 数据访问

在JavaScript中有四种基本的数据访问位置:
1.直接量
直接量仅仅代表自己,而不存储于特定位置。JavaScript的直接量包括:字符串,数字,布尔值,对象,数组,函数,正则表达式,具有特殊意义的空值,以及未定义。

2.变量
开发人员使用var关键字创建用于存储数据值

3.数组项
具有数字索引,存储一个JavaScript数组对象。

4.对象成员
具有字符串索引,存储一个JavaScript对象。

每一种数据存储位置都具有特定的读写操作负担。直接量和局部变量的访问速度要远大于数组项和对象成员的访问速度。

管理作用域
作用域链和标识符解析
每一个JavaScript函数都被表示为对象。进一步说,它是一个函数实例。函数对象如其他对象那样,拥有可以编程访问的属性,和一系列不能被程序访问,仅供JavaScript引擎使用的内部属性。其中一个内部属性就是[[Scope]]。
内部[[Scope]]属性包含一个函数被创建的作用域中对象的集合。此集合被称为函数的作用域链,它决定哪些数据可由函数访问。此函数作用域链中的每个对象被称为一个可变对象,每个可变对象都以“键值对”的形式存在。当一个函数创建后,它的作用域链被填充以对象,这些对象代表创建此函数的环境中可访问的数据。

在函数运行过程中,每遇到一个变量,标识符识别过程要决定从哪里获得或者存储数据。此过程搜索运行期上下文的作用域链,查找同名的标识符。

标识符识别性能
在运行前上下文的作用域链中,一个标识符所处的位置越深,它的读写速度就越慢。
全局变量总是处于运行期上下文作用域链的最后一个位置,所以总是最远才能触及的。这也是为什么要尽量减少全局变量的原因之一。

在没有优化JavaScript引擎的浏览器中,最好尽可能使用局部变量。
一个好的经验法则是:用局部变量存储本地范围之外的变量值,如果他们在函数中使用多于一次,考虑下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function initUI(){
    var bd = document.body,
        links = document.getElementsByTagname("a"),
        i=0,
        len = links,length;
    while(i<len){
        update(links[i++]);
    }
    document.getElementById("go-btn").onclick = function(){
        start();
    };
    bd.className = "active";
}

以上代码中,包含了三个对document的引用。可以通过这种方法减轻重复的全局变量访问对性能的影响:首先将全局变量的引用存储在一个局部变量中,然后使用这个局部变量代替全局变量。

改变作用域链
一般来说,一个运行前上下文的作用域链不会被改变。但是,有两种表达式可以在运行时临时改变运行期上下文作用域链。
第一个是with表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function initUI(){
with(document){    //    应该避免使用
    var bd = body,
        links = getElementsByTagname("a"),
        i=0,
        len = links,length;
    while(i<len){
        update(links[i++]);
    }
    getElementById("go-btn").onclick = function(){
        start();
    };
    bd.className = "active";
    }
}

重写后的initUI()版本使用了with()表达式,避免了多次书写“document”。看起来似乎更有效率,
实际上却产生了一个性能问题:

当代码流执行到一个with表达式时,运行前上下文的作用域链被临时改变了。一个新的可变对象将被创建,它包括指定对象的所有属性。此对象被插入到作用域链的前端,意味着现在函数的所有局部变量都被推入第二个作用域链对象中,所以访问代价更高了。
with的作用

第二个是try-catch表达式的catch子句:
当try块发生错误时,程序流程自动转入catch块,并将异常对象推入作用域链前端的一个可变对象中。

1
2
3
4
5
try{
    //some error
}catch{
    //alert error
}

注意,当catch子句执行完毕,作用域链就会返回到原来的状态。
若没有局部变量访问,作用域链临时改变就不会影响代码的性能。

动态作用域
无论是with表达式还是try-catch表达式的catch子句,以及包含()的函数,都被认为是动态作用域。
动态作用域只因代码运行而存在,因此无法通过静态分析(查看代码结构)来确定是否存在动态作用域。

闭包,作用域和内存
闭包是JavaScript最强大的一个方面,它允许函数访问局部范围之外的数据,同时也能够使得变量常驻内存中,实现缓存。不过,有一个性能影响与闭包有关。
由于闭包的[[Scope]]属性包含于运行前上下文作用域链相同的对象引用,会产生副作用。通常,一个函数的激活对象与运行期上下文一同销毁,当涉及闭包时,激活对象就无法销毁了,因为引用仍然存在于闭包[[Scope]]属性中,这就导致了闭包会需要更多的内存开销。

闭包最主要的性能关注点:经常访问一些范围之外的标识符,每次访问都导致一些性能损失。

小结:为了减轻对运行速度的影响,应将常用的域外变量存入局部变量中,然后直接访问局部变量。

对象成员
大多数JavaScript代码以面向对象的形式编写。对象的一个命名成员可以包含任何数据类型。
当一个命名成员引用了一个函数时,它被称作一个方法,而一个非函数的数据则被称为属性。

原型
JavaScript中的对象是基于原型的。原型是其他对象的基础,定义并实现了一个新对象所必须具有的成员。原型对象为所有给定类型的对象实例所共享,因此所有实例共享原型对象的成员。
一个对象通过一个内部属性绑定到它的原型,Firefox,Safari和Chrome支持这一属性:proto;其他浏览器不允许脚本访问这一属性。任何时候创建一个内置类型的实例,这些实例自动拥有一个Object作为它们的原型。

对象有两种成员:实例成员和原型成员。实例成员直接存在于实例自身,而原型成员则从对象原型继承。
处理对象成员的过程:当对象的某个成员被调用时,对成员进行搜索,先从对象实例开始,如果没有找到,则转向搜索原型对象。

通过hasOwnProperty()函数确定一个对象是否具有特定名称的实例成员,若是为实例成员则返回true,若为原型成员则返回false。
通过in操作符可以确定对象是否具有某个名称的成员,实例成员和原型成员都会被返回true。

原型链
对象的原型决定了一个实例的类型。在原型链上搜索成员,每深入一层都会增加性能损失。

嵌套成员
由于对象成员可能包含其他成员,例如:window.location.href。每遇到一个点号,JavaScript引擎就要在对象成员上执行一次解析过程。应尽量避免使用这种方法,同时在一个函数中不应该多次读取同一个对象成员的值,应将该对象成员的值保存在一个局部变量中。

总结
JavaScript中有四种数据访问类型:直接量,变量,数组项,对象成员。
直接量和局部变量访问速度非常快,数组项和对象成员需要更长时间。局部变量比域外变量快,因为局部变量位于作用域链的第一个对象中。全局变量总是最慢的,因为它们总是位于作用域链的最后一环。
要避免使用with表达式和try-catch表达式的catch子句,因为它们改变了运行前上下文的作用域链。
提高JavaScript代码性能的方法:将经常使用的对象成员,数组项和域外变量存入局部变量中,访问局部变量的速度回快于那些原始变量。

第三章 DOM编程

对DOM操作代价昂贵,在富网页应用中通常是一个瓶颈。
主要有三个方面:
1.访问和修改DOM元素;

2.修改DOM元素的样式,造成重绘和重新排版;

3.通过DOM事件处理用户响应

DOM,即文档对象模型,是一个独立于语言的,使用XML和HTML文档操作的应用程序接口(API)。

访问和修改
浏览器通常要求DOM实现和JavaScript实现保持相互独立。这样两个独立的部分以功能接口连接就会带来性能损耗。应尽量减少对DOM的操作次数,尽量保持在ECMAScript中。
修改元素的代价更昂贵,因为它经常导致浏览器重新计算页面的几何变化。最坏的情况是使用循环执行访问或修改元素,特别是在HTML集合中使用循环。

1
2
3
4
5
function innerHTMLLoop(){
    for(var count = 0; count < 15000; count++){
        document.getElementById("here").innerHTML += "a";
    }
}

修改如下,使用局部变量保存更新后的内容,在循环结束时一次性写入:

1
2
3
4
5
6
7
function innerHTMLLoop(){
    var content = "";
    for(var count = 0; count < 15000; count++){
        content +="a";
    }
    document.getElementById("here").innerHTML += content;
}

innerHTML 与 DOM方法比较
两者性能相差不大,但若要更新一大块HTML页面,innerHTML在大多数浏览器中执行更快。

节点克隆
使用DOM方法更新页面内容的另一个途径是克隆已有DOM元素,而不是创建新的,即使用element.cloneNode()代替document.createElement()。
用克隆节点的方法重新生成一个表格,单元格只创建一次,然后重复执行复制操作,这样做能够稍微快了一点。

HTML集合
HTML集合是用于存放DOM节点引用的类数组对象。
下列函数返回的值就是一个集合:
document.getElementsByTagName();
document.getElementsByClassName();//当下大多数浏览器都不能使用

下列属性也属于HTML集合:
document.images 页面中所有的<img>元素
document.links 页面中所有的<a>元素
document.forms 页面中所有的<form>表单
document.forms[0].elements 页面中第一个表单的所有字段

HTML集合是一种类似数组的列表,但不是数组,没有数组的push()或slice()之类的方法,但提供了length属性。
也可以和数组一样通过下标索引访问列表中的元素。
HTML集合实际上在查询文档,当你更新信息时,每次都要重复执行查询操作,效率很低。
在集合遍历或数组遍历时,将length属性存入一个变量中,从而提升运行速度。

访问集合元素时使用局部变量
一般来说,对于任何类型的DOM访问,如果同一个DOM属性或方法被访问一次以上,最好使用一个局部变量缓存此DOM成员。
当遍历一个集合时,第一个优化是将集合引用存储于局部变量,并在循环之外缓存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
39
40
// slow
function collectionGlobal({
    var coll = document.getElementsByTagName('div'),
    len = coll.length,
    name = '';
    for (var count = 0; count < len; count++) {
        name = document.getElementsByTagName('div')[count].nodeName;
        name = document.getElementsByTagName('div')[count].nodeType;
        name = document.getElementsByTagName('div')[count].tagName;
    }
    return name;
};
// faster
function collectionLocal({
    var coll = document.getElementsByTagName('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;
};
// fastest
function collectionNodesLocal({
    var coll = document.getElementsByTagName('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漫谈
抓取DOM
经常需要从一个DOM元素开始,操作周围的元素,或者递归迭代所有的子节点。使用childNodes集合或者使用nextSibling获得每个元素的兄弟节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function testNextSibling(){
    var el = document.getElementById("myDiv");
    var child = el.firstChild,name ="";
    do{
        name = ch.nodeName;
    }while(ch=ch.nexSibling)
    return name;
}
function testChildNodes(){
    var el = document.getElementById("myDiv"),
    ch = el.childNodes,
    len = ch.length,name="";
    for(var count = 0;count < len; count++){
        name = ch[count].nodeName;
    }
    return name;
}

在不同浏览器上,nextSibling和childNodes方法的运行时间基本相等,但在IE中,nextSibling比childNodes好。所以在IE中,用nextSibling更好,其他浏览器随意。

元素节点
DOM属性诸如childNodes,firstChild和nextSibling不区分元素节点和其他类型节点,如注释节点和文本节点。在多数情况下,只有元素节点需要被访问,所以在循环中,应对节点返回类型进行检查,过滤出非元素节点。
但这些检查和过滤不是必要的,许多浏览器提供了API函数只返回元素节点。

选择器API
最新的浏览器提供了querySelectorAll()的原生浏览器DOM函数。该方法比使用JavaScript和DOM迭代并缩小元素列表的方法要快。

1
2
3
var elements = document.querySelectorAll("#menu a");
querySelectorAll()

接收一个CSS选择器字符串参数并返回一个NodeList——由符合条件的节点构成的类数组对象。还可以通过querySelectorAll()一次性获得多类的节点,进行联合查询:

1
2
//获取类名为warning和notice的div元素
var errs = document.querySelectorAll("div.warning,div.notice");

另外还有一个querySelector()选择器API。该函数只返回符合查询条件的第一个节点。

重绘和重排版
当浏览器下载完所有HTML标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据结构:
一棵DOM树:表示页面结构;
一棵渲染树:表示DOM节点如何显示。

一旦DOM树和渲染树构造完毕,浏览器就可以显示页面上的元素了。

当DOM改变影响到元素的几何属性(宽和高),浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变受到影响。浏览器使渲染树上受影响的部分失效,然后重构渲染树。这个过程被称作重排版。

当只改变DOM的样式时(没有改变页面布局),在这种情况下,只需要重绘。

重排版会发生什么?

除了当布局和几何改变时需要重排版,下列情况也需要重排版:
添加或删除可见的DOM元素;
元素位置改变;
元素尺寸改变;
内容改变,例如文本改变或图片被另一个不同尺寸的所替代。
最初的页面渲染;
浏览器窗口改变尺寸。

最小化重绘和重排版
重绘和重排版代价昂贵,所以,提高程序响应速度一个好策略是减少此类操作发生的机会。为减少发生次数,应该将多个DOM和风格改变合并到一个批次中一次性执行。
当需要改变一个元素多个样式属性时,应该使用cssText()方法

1
2
var el = document.getElementById('mydiv');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

以上代码只修改DOM一次,提高了效率。
这个例子中的代码修改 cssText 属性,覆盖已存在的风格信息。如果你打算保持当前的风格,你可以将
它附加在 cssText 字符串的后面。

1
el.style.cssText += '; border-left: 1px;';

批量修改DOM
当你需要对DOM元素进行多次修改时,可以通过以下步骤减少重绘和重排版的次数:

  1. 从文档流中摘除该元素;
  2. 对其应用多重改变;
  3. 将元素带回文档中;

此过程只引发了两次重排版——第一步和第三步。若忽略这两个步骤,则第二步中每次改变都将引发一次重排版。

有三种方法可以将DOM从文档中摘除:
1)隐藏元素,进行修改,然后再显示它;
2)使用一个文档片段在已存DOM之外创建一个子树,然后将它拷贝到文档中;(性能较优)
3)将原始元素拷贝到一个脱离文档的节点中,修改副本,然后覆盖原始元素。

缓冲布局信息
浏览器通过队列化修改和批量运行的方法,尽量减少重排版次数。当你查询布局信息如偏移量、滚动条
位置,或风格属性时,浏览器刷新队列并执行所有修改操作,以返回最新的数值。最好是尽量减少对布局信
息的查询次数,查询时将它赋给局部变量,并用局部变量参与计算。

1
2
3
4
5
6
7
var current = myElement.offsetLeft;    //只查询了布局信息一次
current++
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if (current >= 500) {
    stopAnimation();
}

将元素提出动画流
显示和隐藏部分页面构成页面展开/折叠动画是一种常见的交互模式。它通常包括区域扩大的几何动画,将页面其他部分推向下方。
浏览器需要重排版的部分越小,应用程序的响应速度就越快。而当一个页面顶部的动画推移了差不多整个页面时,将引发巨大的重排版动作,使用户感到动画卡顿。渲染树的大多数节点需要被重新计算,它变得更糟糕。

使用以下步骤可以避免大部分页面进行重排版:
1)使用绝对坐标定位页面动画的元素,使它位于页面布局流之外;
2)启动动画元素。当它扩大时,它临时覆盖部分页面。这是一个重绘的过程,但只影响页面的一小部分,避免重排版并重绘一大块动画
3)当动画结束,重新定位,从而只一次下移文档其他元素的位置。

译者注:文字描述比较简单概要,我对这三步的理解如下:
1、页面顶部可以“折叠/展开”的元素称作“动画元素”,用绝对坐标对它进行定位,当它的尺寸改变时,就不会推移页面中其他元素的位置,而只是覆盖其他元素。
2、 展开动作只在“动画元素”上进行。 这时其他元素的坐标并没有改变, 换句话说, 其他元素并没有因为“动画元素”的扩大而随之下移,而是任由动画元素覆盖。
3、“动画元素”的动画结束时,将其他元素的位置下移到动画元素下方,界面“跳”了一下。

IE和 :hover
如果大量使用了伪类:hover,那么会降低反应速度。此问题在IE8中尤为显著。

事件托管(事件委托)
当页面中存在大量元素,而且每个元素有一个或多个事件句柄与之挂接(例如onclick)时,可能会影响性能。连接每个句柄都是有代价的。挂接事件占用了处理时间,另外,浏览器需要保存每个句柄的记录,占用更多内存
事件托管基于这样一个事实:事件逐层冒泡总能被父元素捕获。采用事件托管技术之后,你只需要在一个包装元素上挂接一个句柄,用于处理子元素发生的所有事件。
DOM事件的三个阶段:捕获阶段,目标阶段,冒泡阶段

总结
将DOM和ECMAScript比作两座岛,每次操作DOM都要收取“过路费”,因此为减少DOM编程中的性能损失,记住以下几点:
1)最小化DOM访问,尽量在JavaScript端做尽可能多的事情;
2)在反复访问的地方使用局部变量存放DOM引用;
3)小心地处理 HTML 集合,因为他们表现出“存在性”,总是对底层文档重新查询。将集合的 length 属性缓存到一个变量中,在迭代中使用这个变量。如果经常操作这个集合,可以将集合拷贝到数组中;
4)在浏览器允许的情况下,尽可能使用速度更快的API,如querySelectorAll和firstElementChild等;
5)注意重绘和重排版;批量修改风格(cssText),离线操作DOM,缓存并减少对布局信息的访问;
6)动画中使用绝对定位,使用拖放代理
7)使用事件托管技术最小化事件句柄数量

坚持原创技术分享,您的支持将鼓励我继续创作!