javascript笔记:通过对作用域链和执行环境的深入理解所得出的提高javascript代码性能的建议
上篇文章里我结束了对象的创建的内容,最后引出了作用域链和执行环境的问题。当我对这块知识有了更深入的了解后,回头看看jQuery源码才知道大师们写的代码是如何的厉害,jQuery源码里很好的运用了作用域链和执行环境的知识来提升程序性能。
好了,不废话了,上篇博文里对作用域讲的比较简略。其实对作用域的理解是理解整个javascript语言的关键所在,特别我在写javascript笔记时候曾对很多怪异的javascript用法无法理解的透彻,究其原因还是没有真正理解javascript里作用域的概念。
Javascript里的作用域到底决定了什么呢?作用域决定了那些变量能被函数所访问(注意:作用域是函数的内部属性,谈到作用域是绕不开的function),作用域也确定了this指针的指向。上篇博文里我说道,程序其实就是不断检索数据的过程,那么检索数据的效率决定了程序的性能,因此作用域既然决定那些变量能被访问,当然也决定了检索这些变量的方式,所以想写出高效的javascript程序灵活运用作用域的原理是关键了。
上篇博文里面,我写了一个函数,代码如下:
<script type="text/javascript">
function add(a,b)
{
var sum = a + b;
return sum;
}
</script>
大家可以看到这个函数属于window而非function,在页面被加载时候,add函数会被初始化,生成属于自己的作用域链,上篇文章里我在firebug里设定断点调试贴出了一个我截图下来的变量图,认为这就是函数add的Scope所包含的变量,现在发现当时理解是错误的,那些变量是window的“全局环境”的变量图而非是add函数的内部属性Scope的变量图。在此我纠正一下。
下面我要着重讲讲作用域链和执行环境,这块知识我在javascript对象创建的中篇里提到过,这里我将那些知识回顾下:
什么是执行环境呢?在javascript里面执行环境分为两类,一类是全局环境,一类是局部环境,整个页面里被共享的方法和属性就是在全局环境,相对于全局环境,函数{}号里的执行环境就是局部环境,执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为,每个执行环境都定义了一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象里,虽然我们自己编写的代码无法访问这个对象,但解析器在处理数据时候后台会使用到它。
全局执行环境另一种说法是最外围的一个执行环境,在web浏览器的范围中(actionscript也是施行了ECMAScript标准,它的全局范围就和javascript的全局范围不同),全局执行环境被认为是window对象,因此全局变量和函数都是作为window对象的方法和属性来创建的,全局执行环境知道应用程序退出比如关闭网页或浏览器才会被销毁。而局部环境则是以函数对象作为关联对象。
大家要注意环境的前缀是:执行,也就说只有函数被执行(局部执行环境)和页面被加载(全局执行环境),下面就是页面被加载时候的全局执行环境变量:
现在我们要执行add函数了,代码如下:
function add(a,b)
{
var sum = a + b;
return sum;
}
console.log(add(10,20));
这是add函数的执行环境的变量图是:
1.firebug:
2.chrome的代码调试器:
代码的结构图如下:
在函数被执行时候,函数会创建一个“运行期上下文(execution context)”的内部对象,这个运行期上下文在我的理解里就是函数的执行环境,每个运行期上下文都有自己的作用域链,用于标识符解析。
当运行期上下文被创建时候,他的作用域链初始化为当前运行函数【Scope】属性中所含的对象(见firebug和chrome调试器里的内存图,比如add函数除了a,b,sum,this还有arguments,不过这个在firebug和chrome调试器里看不到的)。这些值按照它们出现在函数中的顺序,被复制到执行期上下文的作用域链中。这个过程一旦完成,一个被称为“活动对象(activation object)”的新对象就为执行期上下文创建好了,活动对象作为函数运行期的可变对象,包含了所有局部变量,命名参数,参数集合以及this。然后此对象被推入到作用域链的前端。当运行期上下文被销毁的时候,活动对象也随之被销毁。在函数执行过程中,每遇到一个变量都会进行一次标识符的解析,这种解析决定了我们从啥地方获取变量或者将变量存储到什么地方,这个过程是搜索整个函数运行期上下文的作用域链,查找同名的标识符,搜索过程都是从作用域头部开始,也就是当前运行函数的活动对象。找到了就使用它,没有找到则会继续搜索作用域链中的下一个对象,搜索过程会延续到找到标识符为止或者是没有找到为止,这种情况就是标识符没有被定义了。
在javascript里使用变量就是在做标识符解析,由上面的解释我们知道标识符的解析一定是有计算机性能的消耗的,当标识符位于执行上下文作用域链的位置越深,性能也就越慢了,那么到底什么地方最慢了?比如我们举例的函数add,当我们搜索到global全局变量总会比函数内部的局部变量慢,假如我们定义个更复杂的函数里面有多层嵌套的话,访问全局变量就是性能的梦魇了。因此在函数中我们尽量多使用函数内部的局部变量。(由于浏览器的产品的差异,个别浏览器的访问变量的性能可能会有差异,但是总体而言,访问函数局部变量永远是最快的)。
尽量使用局部变量,就带来一个十分重要的提高程序性能的用法:我们在函数内部使用全局变量可以说是一种跨作用域操作,如果某个跨作用域的值在函数的内部被使用到一次以上,那么我们就把它存储到局部变量里。
代码书写的格式就是:
function ftn()
{
var doc = document;
.......
}
将全局的变量用var定义到局部变量里。
这个用法在jQuery源码里一开头就清晰可以到看:
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
},
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$,
// Use the correct document accordingly with window argument (sandbox)
document = window.document,
jQuery把经常使用的全局变量都存储在函数的局部变量里。