[原]JavaScript必备知识系列-作用域

执行环境和作用域

执行环境(execution context)是javascript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但是解析器在处理数据时会在后台使用它。

全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为是window对象,因为所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境知道应用程序退出-例如关闭网页或浏览器-时才会被销毁)。

局部执行环境,每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMASript,程序中的执行流正是由这个方便的机制控制着。

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保障队执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终都是在当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个对象,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境,这样,一直延续到全局执行环境;全局执行环境的变量始终都是作用域链中的最后一个对象。

标示符解析是沿着作用域链一级一级地搜索标示符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标示符为止。

作用域链的本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

延长作用域链

虽然执行环境的类型总共只有两种—全局和局部,但还是有其他方法来延长作用域链。这么说是应为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。在两种情况下。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会得意加长:

Try-catch语句的catch块;

with语句

这两个语句都会在作用域链的前端添加一个变量对象。队with语句来说,会将制定的对象添加到作用域链中。队catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

改变函数作用域

每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途就是在特定的作用域中调用函数,实际上等于设置函数体内的this对象的值。

函数绑定:一个日益流行,在很多插件(jquery, backbone)中都能见到的一个技巧。函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。它常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。

ECMAScript 5为所有函数定义了一个原生的bind()方法,进一步简单了操作。

没有块级作用域

JavaScript没有块级作用域。在其他类C的语言中,由花括号封闭的代码块都有自己的作用域(如果用ECMAScriptd的话来讲,就是它们自己的执行环境),因而支持根据条件来定义变量。

if(true) {

    var color = ‘blue’;

}

for(var i = 0; i < 10; i++) {

    doSomething(i);

}

If for语句中的变量声明会将变量添加到当前的执行环境,for循环执行结束后,也依旧会存在于循环外部的执行环境中。

使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最饥饿的环境就是环境的局部环境;在with语句中,最接近的是函数环境。如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境。

模仿块级作用域

通过匿名函数可以用来模仿块级作用域,语法如下:

(function() {

    //这里是块级作用域

})()

以上代码定义并理解调用了一个匿名函数。将函数声明包含在一个圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一个圆括号会立即调用这个函数。

可以这样理解匿名函数:

var someFunction = function() {\

    //这里是块级作用域

};

someFunction();

首先定义了一个函数,然后立即调用它。定义函数的方式是创建一个匿名函数,并把匿名函数复制给变量someFunction。而调用函数的方式是在函数名称后面添加一对圆括号,即someFunction()。那在这里是不是也可以用函数的值直接取代函数名呢?

function() {

    //这里是块级作用域

}(); //报错!

这段代码会导致语法错误,是因为JavaScript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换为函数表达式只要给它家还是那个一对圆括号即可。

(function() {

    //这里是块级作用域

})();

实际应用中,这样写法也可以

!function() {

}();

开头叹号“!“也可以换成”;” “+” “-”等。只不过阅读起来不如上面那种形式好理解。

PS:函数声明与函数表达式

函数声明与函数表达式区别在于:解析器子向执行环境中加载数据时对它们并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可以访问,这个重要特征就是函数声明提升(function declaration hoisting);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

posted @ 2012-09-12 21:47  雨知  阅读(2086)  评论(5编辑  收藏  举报