1.作用域和作用域链
在JavaScript中,函数也是对象。对一个函数,每一个能被该函数访问的上下文对象称为该函数的作用域。作用域中的所有属性和方法都能被该函数访问到。在下面的代码中,函数f1和f2的作用域为全局活动对象,而函数f3的作用域为函数f2活动对象和全局活动对象。
var i=5; funtion f1(){ console.log(i); console.log(j); } function f2(){ var j=0; console.log("hello world"); function f3(){ console.log(j); } }
每个函数都有一个[[Scope]]的内部属性,仅供JavaScript引擎访问。这个[[Scope]]指向该函数的作用域链。当一个函数被定义时,该函数能访问到的作用域就依次被放入该函数的作用域链中。此时,作用域链中的第一个对象是该函数定义时的包含环境(一般是其父函数的活动对象),第二个对象是上一层包含环境,一直到全局对象。最后,该函数的[[Scope]]属性将会指向该作用域链。
在某函数被调用时,一个名为“运行期上下文”的内部对象被创建,该对象定义了函数执行时的环境。每一个运行期上下文被创建时,它的作用域链被初始化为当前运行函数的作用域链。还有一个“活动对象”被创建,该活动对象里存放着函数被调用时的形参、函数里的所有局部变量和this,对于没有被赋值的参数会初始化为undefine。然后该活动对象被推入运行期上下文的作用域链头部。当函数执行完毕时,该运行期上下文被销毁,对应的活动对象也随之销毁。
2 标识符解析
在函数执行过程中,每遇到一个变量,都会进行一次标识符解析。从运行期上下文的作用域链头部开始,查找同名的变量,找到了就使用该变量。如果没有就查找作用域链中的下一个对象,直到找到为止。所以全局变量会被同名的局部变量覆盖。
3 执行时改变作用域链
虽然函数的作用域链在定义时已经被[[Scope]]绑定了,但是Js中有两种方法可以在函数执行时临时改变函数的作用域链(其实改变的是当前运行期上下文的作用域链)。
with语句
当遇到with语句时,with后面圆括号中的对象将被临时的推入作用域链的头部,当with语句后面的代码块执行完毕时,该对象会被从作用域链中删除。
funtion f4(){ var t="hello"; with(obj){//在这一句obj将被加入到作用域链头部 console.log(a); } //在这里obj从作用域链中删除
console.log(t);
}
try...catch语句
当遇到try...catch语句时,程序首先执行try代码块中的代码,如果发生错误则跳到catch代码块,此时异常对象将被临时地推入到作用域链的头部。
function f5(){ try{doSomething();} catch (e) {//这里异常对象将被加入到作用域链头部 console.log(e); }//这里异常对象将被从作用域链中删除 }
4 在函数开始定义局部变量
虽然说Js是函数作用域,在某个函数内的任何地方定义一个局部变量该函数都能访问到,不过还是有理由在函数的开始定义局部变量。前面说过,活动对象里没有被赋值的参数会初始化为undefine,在遇到赋值语句后,变量才被赋值。那么在类似下面的代码中,将发生不好的事情。标识符解析时,在echo的活动对象中找到了say变量,完全的覆盖了全局活动对象中的say变量。而say变量没有被赋值,所以才会发生下面的事情。
var say = "hello!"; function echo() { console.log(say);//undefine var say = "hi";//将"hi"赋值给say变量 console.log(say);//hi } echo();
5 性能优化
减少全局对象的使用
由于全局对象始终处于作用域链的尾部,在标识符解析时总是最后一个被查找的对象,查找效率可想而知。记住,如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。
尽量不用with
with虽然可以省略长长的变量名,但是却带来了其它的性能问题。当进入with代码块时,活动对象变成了作用域链的第二个对象,增加了对函数局部变量的访问开销。所以with代码块中最好只使用被with绑定的对象的属性和方法。另外,with也不利于阅读代码。
function f(x, o) { with (o) print(x); }
f被调用时,
x有可能能取到值,也可能是undefined
,如果能取到, 有可能是在o上取的值,也可能是函数的第一個参数x的值(如果o中沒有這個属性的話)。如果你忘记在作为第二個参数的对象o中定义x這個属性
,程序并不會报错,只是取到另一个值而已。
优化try...catch语句
在catch代码块中尽量只访问异常对象,而减少对局部变量的访问。
参考资料:
1.http://www.laruence.com/2009/05/28/863.html
2.http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html
3.https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/with