问题
先看一个例子:
var name = "name";
function echo() {
alert(name);
var name = "anotherName";
alert(name);
alert(age);
}
echo();
大家觉得这个东西的输出是什么?
估计很多人会觉得是:
name
eve
[脚本出错]
大家的分析过程可能是这样的:
在echo中,第一次alert的时候,会去到全局变量name的值,而第二次值被局部变量name覆盖,所以第二次alert是”eve”.而age属性没有定义,所以脚本会出错.
然而,事实并非如此:
undefined
eve
[脚本出错]
可是why?
作用域链
想知道why,首先我们得先知道JavaScript中的作用域的原理以及什么是作用域链(scope chain).
在JavaScript权威指南里面有这么一句话说的很好: JavaScript中的函数是运行在他们被定义的作用域里,而不是他们被执行的作用域里.
在JavaScript中,作用域的概念和其他语言差不多,每次调用函数,就会进入一个函数内的作用域,当从函数返回以后,就返回调用前的作用域.任何执行上下文时刻的作用,都是由作用域链来实现的.大致过程如下:
- 在一个函数被定义的时候,会将他定义时刻的scope chain链接到这个函数对象的[[scope]]属性.
- 在一个函数对象被调用的时候,会创建一个活动对象,然后对于每一个函数的形参,都命名为该活动对象的命名属性,然后将这个活动对象做为此时的作用域链(scope chain)最前端,并将这个函数对象[[scope]]加入到scope chain中.
举个例子说明一下:
var func = function(a, b) {
var name = "name";
... ...
}
func();
我们来分析一下上面这段代码.
在执行func的定义语句的时候,会创建一个这个函数对象的[[scope]]属性,并将这个[[scope]]属性,链接到定义它的作用域链上,此时因为func定义在全局环境,所以此时的[[scope]]只是指向全局活动对象window active object.
在调用func的时候,会创建一个活动对象,我们假设他为object,并创建实参对象(arguments),然后会给这个对象添加俩命名属性object.a,object.b;对于每一个在这个函数中申明的局部变量和函数定义,都作为该活动对象同名命名属性.
然后将调用参数赋值给形参,对于缺少的实参,赋值为undefined.
然后将这个活动对象作为scope chain的最前端,并将func的[[scope]]属性所指向的,定义func时候的顶级活动对象加入到scope chain.
有了上面的作用域链,在发生标识符解析的时候,就会逆向查询当前的scope chain列表每一个活动对象的属性,如果找到同名的就返回,招不到就标识undefined
注意,因为函数对象的[[scope]]属性是在定义一个函数的时候决定的,而非调用函数的时候,所以如下面的例子:
var name = "name";
function echo() {
alert(name);
}
function env() {
var name = "eve";
echo();
}
env();
运行的结果是:
name
再来一个例子:
function factory() {
var name = "x";
var intro = function() {
alert('Hello ' + name);
}
return intro;
}
function app(para) {
var name = para;
var func = factory();
func();
}
app("eve");
当调用app的时候,scope chain是由:{window活动对象(全局)} -> {app的活动对象}组成.
在刚进入app函数体时,app的活动对象有一个arguments属性,两个值为undefined的属性:name和fund.和一个值为’eve'的属性para;此时scope chain如下:
[[scope chain]] = [
{
para : 'eve',
name : undefined,
func : undefined,
arguments : []
},{
window call object
}
]
当调用进入factory的函数体的时候,此时的factory的scope chain为:
[[scope chain]] = [
{
name : undefined,
intro : undefined
},{
window call object
}
]
请注意,此时的作用域链,并不包含app的活动对象.
在定义intro函数的时候,intro函数的[[scope]]为:
[[scope chain]] = [
{
name : 'x',
intro ; undefined
},{
window call object
}
]
从factory函数返回以后,在app体内调用intro的时候,发生了标识符解析,而此时的scope chain是:
[[scope chain]] = [
{
intro call object
},{
name : 'x',
intro : undefined
},{
window call object
}
]
因为scope chain中,并不包含factory活动对象,所以,name标识符解析的结果应该是factory活动对象中的name属性,也就是’x’.
所以运行结果应该是:
Hello x
所以请记住:
JavaScript中的函数运行在他们被定义的作用域里,而不是他们被执行的作用域里!!!
生命不息,折腾不止...
I'm not a real coder,but i love it so much!