問題
先看一個(gè)例子:
var name = "name";
function echo() {
alert(name);
var name = "anotherName";
alert(name);
alert(age);
}
echo();
大家覺得這個(gè)東西的輸出是什么?
估計(jì)很多人會(huì)覺得是:
name
eve
[腳本出錯(cuò)]
大家的分析過程可能是這樣的:
在echo中,第一次alert的時(shí)候,會(huì)去到全局變量name的值,而第二次值被局部變量name覆蓋,所以第二次alert是”eve”.而age屬性沒有定義,所以腳本會(huì)出錯(cuò).
然而,事實(shí)并非如此:
undefined
eve
[腳本出錯(cuò)]
可是why?
作用域鏈
想知道why,首先我們得先知道JavaScript中的作用域的原理以及什么是作用域鏈(scope chain).
在JavaScript權(quán)威指南里面有這么一句話說的很好: JavaScript中的函數(shù)是運(yùn)行在他們被定義的作用域里,而不是他們被執(zhí)行的作用域里.
在JavaScript中,作用域的概念和其他語言差不多,每次調(diào)用函數(shù),就會(huì)進(jìn)入一個(gè)函數(shù)內(nèi)的作用域,當(dāng)從函數(shù)返回以后,就返回調(diào)用前的作用域.任何執(zhí)行上下文時(shí)刻的作用,都是由作用域鏈來實(shí)現(xiàn)的.大致過程如下:
- 在一個(gè)函數(shù)被定義的時(shí)候,會(huì)將他定義時(shí)刻的scope chain鏈接到這個(gè)函數(shù)對(duì)象的[[scope]]屬性.
- 在一個(gè)函數(shù)對(duì)象被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)活動(dòng)對(duì)象,然后對(duì)于每一個(gè)函數(shù)的形參,都命名為該活動(dòng)對(duì)象的命名屬性,然后將這個(gè)活動(dòng)對(duì)象做為此時(shí)的作用域鏈(scope chain)最前端,并將這個(gè)函數(shù)對(duì)象[[scope]]加入到scope chain中.
舉個(gè)例子說明一下:
var func = function(a, b) {
var name = "name";
... ...
}
func();
我們來分析一下上面這段代碼.
在執(zhí)行func的定義語句的時(shí)候,會(huì)創(chuàng)建一個(gè)這個(gè)函數(shù)對(duì)象的[[scope]]屬性,并將這個(gè)[[scope]]屬性,鏈接到定義它的作用域鏈上,此時(shí)因?yàn)閒unc定義在全局環(huán)境,所以此時(shí)的[[scope]]只是指向全局活動(dòng)對(duì)象window active object.
在調(diào)用func的時(shí)候,會(huì)創(chuàng)建一個(gè)活動(dòng)對(duì)象,我們假設(shè)他為object,并創(chuàng)建實(shí)參對(duì)象(arguments),然后會(huì)給這個(gè)對(duì)象添加倆命名屬性object.a,object.b;對(duì)于每一個(gè)在這個(gè)函數(shù)中申明的局部變量和函數(shù)定義,都作為該活動(dòng)對(duì)象同名命名屬性.
然后將調(diào)用參數(shù)賦值給形參,對(duì)于缺少的實(shí)參,賦值為undefined.
然后將這個(gè)活動(dòng)對(duì)象作為scope chain的最前端,并將func的[[scope]]屬性所指向的,定義func時(shí)候的頂級(jí)活動(dòng)對(duì)象加入到scope chain.
有了上面的作用域鏈,在發(fā)生標(biāo)識(shí)符解析的時(shí)候,就會(huì)逆向查詢當(dāng)前的scope chain列表每一個(gè)活動(dòng)對(duì)象的屬性,如果找到同名的就返回,招不到就標(biāo)識(shí)undefined
注意,因?yàn)楹瘮?shù)對(duì)象的[[scope]]屬性是在定義一個(gè)函數(shù)的時(shí)候決定的,而非調(diào)用函數(shù)的時(shí)候,所以如下面的例子:
var name = "name";
function echo() {
alert(name);
}
function env() {
var name = "eve";
echo();
}
env();
運(yùn)行的結(jié)果是:
name
再來一個(gè)例子:
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");
當(dāng)調(diào)用app的時(shí)候,scope chain是由:{window活動(dòng)對(duì)象(全局)} -> {app的活動(dòng)對(duì)象}組成.
在剛進(jìn)入app函數(shù)體時(shí),app的活動(dòng)對(duì)象有一個(gè)arguments屬性,兩個(gè)值為undefined的屬性:name和fund.和一個(gè)值為’eve'的屬性para;此時(shí)scope chain如下:
[[scope chain]] = [
{
para : 'eve',
name : undefined,
func : undefined,
arguments : []
},{
window call object
}
]
當(dāng)調(diào)用進(jìn)入factory的函數(shù)體的時(shí)候,此時(shí)的factory的scope chain為:
[[scope chain]] = [
{
name : undefined,
intro : undefined
},{
window call object
}
]
請(qǐng)注意,此時(shí)的作用域鏈,并不包含app的活動(dòng)對(duì)象.
在定義intro函數(shù)的時(shí)候,intro函數(shù)的[[scope]]為:
[[scope chain]] = [
{
name : 'x',
intro ; undefined
},{
window call object
}
]
從factory函數(shù)返回以后,在app體內(nèi)調(diào)用intro的時(shí)候,發(fā)生了標(biāo)識(shí)符解析,而此時(shí)的scope chain是:
[[scope chain]] = [
{
intro call object
},{
name : 'x',
intro : undefined
},{
window call object
}
]
因?yàn)閟cope chain中,并不包含factory活動(dòng)對(duì)象,所以,name標(biāo)識(shí)符解析的結(jié)果應(yīng)該是factory活動(dòng)對(duì)象中的name屬性,也就是’x’.
所以運(yùn)行結(jié)果應(yīng)該是:
Hello x
所以請(qǐng)記住:
JavaScript中的函數(shù)運(yùn)行在他們被定義的作用域里,而不是他們被執(zhí)行的作用域里!!!
生命不息,折騰不止...
I'm not a real coder,but i love it so much!