深入理解javascript作用域第二篇之詞法作用域和動(dòng)態(tài)作用域
前面的話
大多數(shù)時(shí)候,我們對(duì)作用域產(chǎn)生混亂的主要原因是分不清楚應(yīng)該按照函數(shù)位置的嵌套順序,還是按照函數(shù)的調(diào)用順序進(jìn)行變量查找。再加上this機(jī)制的干擾,使得變量查找極易出錯(cuò)。這實(shí)際上是由兩種作用域工作模型導(dǎo)致的,作用域分為詞法作用域和動(dòng)態(tài)作用域,分清這兩種作用域模型就能夠?qū)ψ兞坎檎疫^(guò)程有清晰的認(rèn)識(shí)。本文是深入理解javascript作用域系列第二篇——詞法作用域和動(dòng)態(tài)作用域
詞法作用域
第一篇介紹過(guò),編譯器的第一個(gè)工作階段叫作分詞,就是把由字符組成的字符串分解成詞法單元。這個(gè)概念是理解詞法作用域的基礎(chǔ)
簡(jiǎn)單地說(shuō),詞法作用域就是定義在詞法階段的作用域,是由寫(xiě)代碼時(shí)將變量和塊作用域?qū)懺谀睦飦?lái)決定的,因此當(dāng)詞法分析器處理代碼時(shí)會(huì)保持作用域不變
關(guān)系
無(wú)論函數(shù)在哪里被調(diào)用,也無(wú)論它如何被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時(shí)所處的位置決定
function foo(a) { var b = a * 2; function bar(c) { console.log( a, b, c ); } bar(b * 3); } foo( 2 ); // 2 4 12
在這個(gè)例子中有三個(gè)逐級(jí)嵌套的作用域。為了幫助理解,可以將它們想象成幾個(gè)逐級(jí)包含的氣泡
作用域氣泡由其對(duì)應(yīng)的作用域塊代碼寫(xiě)在哪里決定,它們是逐級(jí)包含的
氣泡1包含著整個(gè)全局作用域,其中只有一個(gè)標(biāo)識(shí)符:foo
氣泡2包含著foo所創(chuàng)建的作用域,其中有三個(gè)標(biāo)識(shí)符:a、bar和b
氣泡3包含著bar所創(chuàng)建的作用域,其中只有一個(gè)標(biāo)識(shí)符:c
查找
作用域氣泡的結(jié)構(gòu)和互相之間的位置關(guān)系給引擎提供了足夠的位置信息,引擎用這些信息來(lái)查找標(biāo)識(shí)符的位置
在代碼片段中,引擎執(zhí)行console.log(...)聲明,并查找a、b和c三個(gè)變量的引用。它首先從最內(nèi)部的作用域,也就是bar(...)函數(shù)的作用域開(kāi)始查找。引擎無(wú)法在這里找到a,因此會(huì)去上一級(jí)到所嵌套的foo(...)的作用域中繼續(xù)查找。在這里找到了a,因此引擎使用了這個(gè)引用。對(duì)b來(lái)講也一樣。而對(duì)c來(lái)說(shuō),引擎在bar(...)中找到了它
[注意]詞法作用域查找只會(huì)查找一級(jí)標(biāo)識(shí)符,如果代碼引用了foo.bar.baz,詞法作用域查找只會(huì)試圖查找foo標(biāo)識(shí)符,找到這個(gè)變量后,對(duì)象屬性訪問(wèn)規(guī)則分別接管對(duì)bar和baz屬性的訪問(wèn)
foo = { bar:{ baz: 1 } }; console.log(foo.bar.baz);//1
遮蔽
作用域查找從運(yùn)行時(shí)所處的最內(nèi)部作用域開(kāi)始,逐級(jí)向外或者說(shuō)向上進(jìn)行,直到遇見(jiàn)第一個(gè)匹配的標(biāo)識(shí)符為止
在多層的嵌套作用域中可以定義同名的標(biāo)識(shí)符,這叫作“遮蔽效應(yīng)”,內(nèi)部的標(biāo)識(shí)符“遮蔽”了外部的標(biāo)識(shí)符
var a = 0; function test(){ var a = 1; console.log(a);//1 } test();
全局變量會(huì)自動(dòng)為全局對(duì)象的屬性,因此可以不直接通過(guò)全局對(duì)象的詞法名稱(chēng),而是間接地通過(guò)對(duì)全局對(duì)象屬性的引用來(lái)對(duì)其進(jìn)行訪問(wèn)
var a = 0; function test(){ var a = 1; console.log(window.a);//0 } test();
通過(guò)這種技術(shù)可以訪問(wèn)那些被同名變量所遮蔽的全局變量。但非全局的變量如果被遮蔽了,無(wú)論如何都無(wú)法被訪問(wèn)到
動(dòng)態(tài)作用域
javascript使用的是詞法作用域,它的最重要的特征是它的定義過(guò)程發(fā)生在代碼的書(shū)寫(xiě)階段
那為什么要介紹動(dòng)態(tài)作用域呢?實(shí)際上動(dòng)態(tài)作用域是javascript另一個(gè)重要機(jī)制this的表親。作用域混亂多數(shù)是因?yàn)樵~法作用域和this機(jī)制相混淆,傻傻分不清楚
動(dòng)態(tài)作用域并不關(guān)心函數(shù)和作用域是如何聲明以及在任何處聲明的,只關(guān)心它們從何處調(diào)用。換句話說(shuō),作用域鏈?zhǔn)腔谡{(diào)用棧的,而不是代碼中的作用域嵌套
var a = 2; function foo() { console.log( a ); } function bar() { var a = 3; foo(); } bar();
【1】如果處于詞法作用域,也就是現(xiàn)在的javascript環(huán)境。變量a首先在foo()函數(shù)中查找,沒(méi)有找到。于是順著作用域鏈到全局作用域中查找,找到并賦值為2。所以控制臺(tái)輸出2
【2】如果處于動(dòng)態(tài)作用域,同樣地,變量a首先在foo()中查找,沒(méi)有找到。這里會(huì)順著調(diào)用棧在調(diào)用foo()函數(shù)的地方,也就是bar()函數(shù)中查找,找到并賦值為3。所以控制臺(tái)輸出3
小結(jié):兩種作用域的區(qū)別,簡(jiǎn)而言之,詞法作用域是在定義時(shí)確定的,而動(dòng)態(tài)作用域是在運(yùn)行時(shí)確定的
以上所述是小編給大家介紹的深入理解javascript作用域第二篇之詞法作用域和動(dòng)態(tài)作用域,希望對(duì)大家有所幫助,如果大家想了解更多內(nèi)容敬請(qǐng)關(guān)注腳本之家!
相關(guān)文章
淺析JS動(dòng)態(tài)創(chuàng)建元素【兩種方法】
下面小編就為大家?guī)?lái)一篇淺析JS動(dòng)態(tài)創(chuàng)建元素【兩種方法】。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家。一起跟隨小編過(guò)來(lái)看看吧2016-04-04JavaScript 井字棋人工智能實(shí)現(xiàn)代碼
JavaScript fights back in this artificial Tic Tac Toe game. Great script to have to entertain yourself and your visitors.2009-12-12Radio 單選JS動(dòng)態(tài)添加的選項(xiàng)onchange事件無(wú)效的解決方法
radio 單選JS動(dòng)態(tài)添加的選項(xiàng),onchange事件無(wú)效。使用delegate()函數(shù)可以解決該問(wèn)題,具體解決方案大家通過(guò)本文詳細(xì)了解下2016-12-12