高性能Javascript【二】数据访问
2011-09-01 21:19
155 查看
【本文系外部转载,原文地址:http://www.hotels2map.com/blog/?p=116】
程序运行的快慢和程序运行中数据的读写分不开,也就是数据的访问,而数据的读写效率很大程度上取决于数据的存取位置。
Javascript中常见的基本数据存储有:直接量、变量、数组元素、对象成员,访问直接量和局部变量的速度最快,数组元素和对象成员相对较慢。
直接量只代表她本省,不存在任何特定位置,因此他的访问速度是最快的,马上返回数据。
变量是用var定义的数据存储单元,她的访问速度和他的变量的作用域有关,局部变量的访问速度和直接量接近,也是非常快的。
数组元素和对象成员,因为需要根据索引来在数组或者对象中来查找,而且对象存在原型链,因此访问的速度要比直接量和变量慢,其速度和具体的浏览器有关。
可以通过以下方法来优化数据访问的性能:
可以通过把常量的对象成员、数组元素、跨域变量保存在局部变量中来,因为局部变量访问速度更快。
http://www.nczonline.net/blog/2009/02/10/javascript-variable-performance/
控制原型链的深度。
减少对象的嵌套成员。
缓存对象成员。
函数创建时的作用域链
当一个函数创建的时候,作用域链就会被一系列的对象填充,作用域链中的每一个对象被称为一个可变对象,这些对象表示了在此函数作用域内哪些数据可以被访问到。比如上面的add函数,创建的时候,作用域链中被被唯一的一个可变对象填充:全局对象,包括document、window等被全局定义的变量;
函数执行的作用域链
当一个函数执行的时候,会创建一个“执行期上下文”(execution content),作用域链初始化,创建“活动对象”(activation object),并且把活动对象添加到作用域链的头部,活动对象包含了局部变量、命名参数、参数几何以及this。
当add(5,10)函数执行的时候,一个执行期上下文被创建,他的作用域链被初始化为函数add(5,10)的[[scope]]属性中的对象,形成活动对象,并把活动对象添加到作用域链的头部,这时候作用域链中就有了2个对象:活动对象,全局对象。
标识符解析(identifier resolution)
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析的过程,来决定从哪里获取数据和把数据存储到哪里。
标识符解析的过程会搜索执行期上下文的作用域链,搜索的过程是从作用域链的头部开始的,也就是从活动对象开始,如果在作用域链的活动对象中找到了需要的标识符就返回,没有则进入作用域链的第二个对象,如此推进,知道找到为止,如果所有的对象中都没有找到,则认为未定义,这个不断搜索的过程是影响性能的关键。
比如上面的add(5,10),执行的时候,遇到num1的时候,会先作用域链的活动对象中找,因为num1是参数,存在于活动对象中,马上就会被返回,如果没有,就会继续进行作用域链搜索,到全局对象中去找。
在执行期上下文的作用域链中,标识符所在的对象的位置越靠后,需要搜索的路径就越长,读写操作的速度就越慢,所以局部变量的读写通常都是最快的,因为他在作用域链的最前面,饿全局变量是最慢的,因为他在作用域链的最后面。
所以优化性能的时候,尽量使用局部变量,如果一个跨作用域的变量被引用一次以上,可以把他存为一个局部变量。
with和try-catch可以改变作用域链,with运行的时候,一个新的属于with的可变对象会被添加到作用域链的头部,形成with的可变对象->活动对象->其他对象的结构,这时标志符解析的速度会变慢,影响性能,try-catch中的catch子句也类似。
此外还有eval函数,这三个都被认为是动态作用域,他们作用域链和具体的环境和代码有关,会致标识符的解析无法预知,因此非必要时不提倡使用。
闭包允许函数访问局部作用域之外的数据,当某个函数中有闭包时,这个函数和闭包会引用作用域链中相同的活动对象,导致这个函数执行完之后,他的活动对象不会马上销毁,会消耗更多的内存,在ie中还会导致内存泄漏。
此外,闭包执行的时候,会产生2个活动对象,一个是闭包本身的活动对象,在作用域链的第一位(下面代码中的this),一个是包含闭包的函数的活动对象,在作用域链的第二位(下面代码中的变量id),之后才是其他的可变对象,而闭包一般都需要在第二个活动对象中获取需要的数据,这样标识符解析的过程会比直接冲第一个活动对象获取慢,导致性能的损失。闭包影响到内存和性能速度,需要小心使用。
Javascript中的对象是基于原型的,一旦创建一个对象实例,她会自动拥有一个Object实例作为原型,许多的属性和方法通过原型自动继承而来的,因此对象成员有2中类型:实例成员和原型成员。
这里的title是book的实例成员(可以通过hasOwnProperty方法来判断),而toString是book的原型成员,如果需要确定某个对象是否(包括实例成员和原型成员)拥有某个成员可以试用in操作。
对象和原型之间是通过__proto__属性来自动绑定的,__proto__属性是不可修改的,但是可以通过prototype构造器来创建另一种类型的原型:
控制原型链的深度
由于原型的这种自动继承机制,一个对象有她的原型对象(一级原型),原型对象又可能有她自己的原型(二级原型)…如此循环;于是当你访问某个实例对象的某个属性的时候,这个属性肯是她的实例成员,也可能是一级原型成员,也可能是二级原型成员,也可能是三级原型成员…可以不断的网上追溯。但是这个属性在原型链上的位置越深,找到她需要经历的路径就越长,访问对象的实例成员的速度本来就没有直接量和变量快,如果再加上原型链的追溯、遍历,速度就更加的慢了。
因为每次遇到点操作,Javascript都会搜索所有的对象成员,如果需要的某个成员嵌套的很深的话,会导致多次搜索,速度也就越慢。因此location.href总是比window.location.href要快。
如果对象成员的值没有变化的话,可以用变量把对象成员缓存起来,减少对对象成员的访问次数。
可以写成这样:
如果一个相同的变量存在于作用域链的不同部分,那么会返回遍历作用域链时最先找到的那个,某些情况可以理解为局部变量会覆盖全局变量。
在大部分的浏览器中用object.name和object["name"]来访问对象成员的速度区别不大,只有在safari中点号始终要快,但是如果成员的名称是变量的时候,中括号才是硬道理。
缓存对象成员的方法适用于对象的属性,不适用与对象的方法,尤其是这个对象的方法中有使用this关键字的时候。
每次看到作用域和闭包相关的理论时都比较晕,以上有些属于个人理解,也可参考下面的文章。
JavaScript对象模型-执行模型:http://www.cnblogs.com/RicCC/archive/2008/02/15/javascript-object-model-execution-model.html
理解 JavaScript 闭包:http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html
学习Javascript闭包(Closure):http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.htm
程序运行的快慢和程序运行中数据的读写分不开,也就是数据的访问,而数据的读写效率很大程度上取决于数据的存取位置。
Javascript中常见的基本数据存储有:直接量、变量、数组元素、对象成员,访问直接量和局部变量的速度最快,数组元素和对象成员相对较慢。
直接量只代表她本省,不存在任何特定位置,因此他的访问速度是最快的,马上返回数据。
变量是用var定义的数据存储单元,她的访问速度和他的变量的作用域有关,局部变量的访问速度和直接量接近,也是非常快的。
数组元素和对象成员,因为需要根据索引来在数组或者对象中来查找,而且对象存在原型链,因此访问的速度要比直接量和变量慢,其速度和具体的浏览器有关。
可以通过以下方法来优化数据访问的性能:
可以通过把常量的对象成员、数组元素、跨域变量保存在局部变量中来,因为局部变量访问速度更快。
http://www.nczonline.net/blog/2009/02/10/javascript-variable-performance/
控制原型链的深度。
减少对象的嵌套成员。
缓存对象成员。
管理作用域和理解对象原型,是优化数据的访问速度的关键。
作用域链(scope chain)
作用域不仅关系到变量的作用范围、this的指向等,也和性能有直接的关系。每一个Javascript函数都有一个内部属性[[scope]],因为函数是对象(一切皆对象),[[scope]] 包含函数创建时的作用域(scope)信息,是一个对象列表,这个集合就是作用域链,作用域链用于标识符的解析,他决定了哪些数据能被函数访问,如何访问;
function add(num1, num2){ var sum = num1 + num2; return sum; }
函数创建时的作用域链
当一个函数创建的时候,作用域链就会被一系列的对象填充,作用域链中的每一个对象被称为一个可变对象,这些对象表示了在此函数作用域内哪些数据可以被访问到。比如上面的add函数,创建的时候,作用域链中被被唯一的一个可变对象填充:全局对象,包括document、window等被全局定义的变量;
var total = add(5,10);
函数执行的作用域链
当一个函数执行的时候,会创建一个“执行期上下文”(execution content),作用域链初始化,创建“活动对象”(activation object),并且把活动对象添加到作用域链的头部,活动对象包含了局部变量、命名参数、参数几何以及this。
当add(5,10)函数执行的时候,一个执行期上下文被创建,他的作用域链被初始化为函数add(5,10)的[[scope]]属性中的对象,形成活动对象,并把活动对象添加到作用域链的头部,这时候作用域链中就有了2个对象:活动对象,全局对象。
标识符解析(identifier resolution)
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析的过程,来决定从哪里获取数据和把数据存储到哪里。
标识符解析的过程会搜索执行期上下文的作用域链,搜索的过程是从作用域链的头部开始的,也就是从活动对象开始,如果在作用域链的活动对象中找到了需要的标识符就返回,没有则进入作用域链的第二个对象,如此推进,知道找到为止,如果所有的对象中都没有找到,则认为未定义,这个不断搜索的过程是影响性能的关键。
比如上面的add(5,10),执行的时候,遇到num1的时候,会先作用域链的活动对象中找,因为num1是参数,存在于活动对象中,马上就会被返回,如果没有,就会继续进行作用域链搜索,到全局对象中去找。
在执行期上下文的作用域链中,标识符所在的对象的位置越靠后,需要搜索的路径就越长,读写操作的速度就越慢,所以局部变量的读写通常都是最快的,因为他在作用域链的最前面,饿全局变量是最慢的,因为他在作用域链的最后面。
所以优化性能的时候,尽量使用局部变量,如果一个跨作用域的变量被引用一次以上,可以把他存为一个局部变量。
function initUI(){ var doc = document, bd = doc.body, links = doc.getElementsByTagName("a"), i= 0, len = links.length; while(i < len){ update(links[i++]); } doc.getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active"; }
改变作用域链
with和try-catch可以改变作用域链,with运行的时候,一个新的属于with的可变对象会被添加到作用域链的头部,形成with的可变对象->活动对象->其他对象的结构,这时标志符解析的速度会变慢,影响性能,try-catch中的catch子句也类似。此外还有eval函数,这三个都被认为是动态作用域,他们作用域链和具体的环境和代码有关,会致标识符的解析无法预知,因此非必要时不提倡使用。
闭包、作用域和内存
闭包允许函数访问局部作用域之外的数据,当某个函数中有闭包时,这个函数和闭包会引用作用域链中相同的活动对象,导致这个函数执行完之后,他的活动对象不会马上销毁,会消耗更多的内存,在ie中还会导致内存泄漏。此外,闭包执行的时候,会产生2个活动对象,一个是闭包本身的活动对象,在作用域链的第一位(下面代码中的this),一个是包含闭包的函数的活动对象,在作用域链的第二位(下面代码中的变量id),之后才是其他的可变对象,而闭包一般都需要在第二个活动对象中获取需要的数据,这样标识符解析的过程会比直接冲第一个活动对象获取慢,导致性能的损失。闭包影响到内存和性能速度,需要小心使用。
function assignEvents(){ var id = "xdi9592"; document.getElementById("save-btn").onclick = function(event){ saveDocument(id); }; }
原型
Javascript中的对象是基于原型的,一旦创建一个对象实例,她会自动拥有一个Object实例作为原型,许多的属性和方法通过原型自动继承而来的,因此对象成员有2中类型:实例成员和原型成员。var book = { title : "High Performance Javascript" } alert(book instanceof Object);//true book是Object的一个实例 alert(book.title); //High Performance Javascript alert(book.toString());//[object Object] //http://www.nczonline.net/blog/2010/07/27/determining-if-an-object-property-exists/ alert(book.hasOwnProperty("title")); //true alert(book.hasOwnProperty("toString")); //false alert("title" in book); //true alert("toString" in book); //true
这里的title是book的实例成员(可以通过hasOwnProperty方法来判断),而toString是book的原型成员,如果需要确定某个对象是否(包括实例成员和原型成员)拥有某个成员可以试用in操作。
原型链
对象和原型之间是通过__proto__属性来自动绑定的,__proto__属性是不可修改的,但是可以通过prototype构造器来创建另一种类型的原型:function Book(title){ this.title = title; } Book.prototype.sayTitle = function(){ alert(this.title); } //book1的原型(__proto__)是Book.prototype,Book.prototype的原型是Object,形成一个原型链 var book1 = new Book("High Performance Javascript"); var book2 = new Book("Javascript: The Good Parts"); //通过prototype创建的原型成员,可以被所有的实例对象继承,即使prototype的原型成员是在实例对象创建之后声明的。 Book.prototype.sayTest = function(){ alert("test=" + this.title); } alert(book1 instanceof Book);//true alert(book1 instanceof Object);//true book1.sayTest();//High Performance Javascript alert(book1.toString());//[object Object]
控制原型链的深度
由于原型的这种自动继承机制,一个对象有她的原型对象(一级原型),原型对象又可能有她自己的原型(二级原型)…如此循环;于是当你访问某个实例对象的某个属性的时候,这个属性肯是她的实例成员,也可能是一级原型成员,也可能是二级原型成员,也可能是三级原型成员…可以不断的网上追溯。但是这个属性在原型链上的位置越深,找到她需要经历的路径就越长,访问对象的实例成员的速度本来就没有直接量和变量快,如果再加上原型链的追溯、遍历,速度就更加的慢了。
减少对象的嵌套成员
因为每次遇到点操作,Javascript都会搜索所有的对象成员,如果需要的某个成员嵌套的很深的话,会导致多次搜索,速度也就越慢。因此location.href总是比window.location.href要快。
缓存对象成员
如果对象成员的值没有变化的话,可以用变量把对象成员缓存起来,减少对对象成员的访问次数。function hasEitherClass(ele,cls1,,cls2){ return ele.className == cls1 || ele.className == cls2; }
可以写成这样:
function hasEitherClass(ele,cls1,,cls2){ var curClass = ele.className;//减少对ele对象的成员className的访问 return curClass == cls1 || curClass == cls2; }
tips:
如果一个相同的变量存在于作用域链的不同部分,那么会返回遍历作用域链时最先找到的那个,某些情况可以理解为局部变量会覆盖全局变量。在大部分的浏览器中用object.name和object["name"]来访问对象成员的速度区别不大,只有在safari中点号始终要快,但是如果成员的名称是变量的时候,中括号才是硬道理。
缓存对象成员的方法适用于对象的属性,不适用与对象的方法,尤其是这个对象的方法中有使用this关键字的时候。
每次看到作用域和闭包相关的理论时都比较晕,以上有些属于个人理解,也可参考下面的文章。
JavaScript对象模型-执行模型:http://www.cnblogs.com/RicCC/archive/2008/02/15/javascript-object-model-execution-model.html
理解 JavaScript 闭包:http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html
学习Javascript闭包(Closure):http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.htm
相关文章推荐
- 《高性能javascript》读书笔记:第二章 数据访问
- 高性能javascript(第二章 数据访问)
- 高性能Javascript 数据访问读书笔记
- 数据存取和访问
- NHibernate+WCF项目实战(二)使用NHibernate实现数据访问并进行单元测试
- 多线程数据访问
- 对访问日志数据进行读取,清洗,分析,绘图和储存
- 动态创建文本文件并写入数据 避免正由另一进程使用,因此该进程无法访问该文件。的问题
- 获取手机APP对网络访问数据的一个思路
- 访问 Microsoft SQL Server 元数据的三种方法
- Microsoft所提供的数据访问技术
- SQL Server 2008使用LINQ进行数据访问(转载自IT168 [ http://www.it168.com/ ])
- 【后台开发拾遗】数据访问、缓存与更新
- android json 访问 服务器数据 思路
- 传统对数据访问与主数据管理(MDM)
- Sharepoint 2010 BDC 之 访问已被业务数据连接拒绝
- [转自microsoft]NET 数据访问架构指南",-数据库连接的测试.即监视链接池化
- 为数据提供返回String形式的编程访问,不只是toString()
- Asp.net中打造通用数据访问类(c#)[转]
- 水晶易表 跨域不能访问数据的解决方案