JavaScript作用域原理

問題

先看一個(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!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末感论,一起剝皮案震驚了整個(gè)濱河市彰檬,隨后出現(xiàn)的幾起案子懂昂,更是在濱河造成了極大的恐慌宝穗,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洲尊,死亡現(xiàn)場(chǎng)離奇詭異晓锻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)肩祥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門后室,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人混狠,你說我怎么就攤上這事岸霹。” “怎么了将饺?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵贡避,是天一觀的道長痛黎。 經(jīng)常有香客問我,道長刮吧,這世上最難降的妖魔是什么湖饱? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮杀捻,結(jié)果婚禮上井厌,老公的妹妹穿的比我還像新娘。我一直安慰自己致讥,他們只是感情好仅仆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垢袱,像睡著了一般墓拜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上请契,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天咳榜,我揣著相機(jī)與錄音,去河邊找鬼姚糊。 笑死贿衍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的救恨。 我是一名探鬼主播贸辈,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼肠槽!你這毒婦竟也來了擎淤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤秸仙,失蹤者是張志新(化名)和其女友劉穎嘴拢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寂纪,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡席吴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捞蛋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孝冒。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拟杉,靈堂內(nèi)的尸體忽然破棺而出庄涡,到底是詐尸還是另有隱情,我是刑警寧澤搬设,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布穴店,位于F島的核電站撕捍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏泣洞。R本人自食惡果不足惜忧风,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望球凰。 院中可真熱鬧阀蒂,春花似錦、人聲如沸弟蚀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽义钉。三九已至,卻和暖如春规肴,著一層夾襖步出監(jiān)牢的瞬間捶闸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國打工拖刃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留删壮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓兑牡,卻偏偏與公主長得像央碟,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子均函,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容