JavaScript作用域学习笔记

时间:2021-07-03 14:47:37

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