JS基礎(chǔ)知識:變量對象、作用域鏈和閉包

JS基礎(chǔ)知識:變量對象畔柔、作用域鏈和閉包


前言:這段時間一直在消化作用域鏈和閉包的相關(guān)知識氯夷。之前看《JS高程》和一些技術(shù)博客,對于這些概念的論述多多少少不太清楚或者不太完整靶擦,包括一些大神的技術(shù)文章腮考。這也給我的學(xué)習(xí)上造成了一些困惑,這幾個概念的理解也是始終處于一個半懂不懂的狀態(tài)玄捕。后來在某公眾號看到了維客館的基礎(chǔ)文章踩蔚,這應(yīng)該是我所看到的最清楚,最全面枚粘,最好懂的文章了馅闽。所以我在學(xué)習(xí)之余決定寫一篇文章,總結(jié)學(xué)到的知識點(diǎn)赌结,用我的理解來闡述颈嚼,不足之處,見請諒解碳却。

執(zhí)行上下文(Execution Context)

也叫執(zhí)行環(huán)境泥兰,也可以簡稱“環(huán)境”。是JS在執(zhí)行過程中產(chǎn)生的量承,當(dāng)JS執(zhí)行一段可執(zhí)行的代碼時搬设,就會生成一個叫執(zhí)行環(huán)境的東西。JS中每個函數(shù)都會有自己的執(zhí)行環(huán)境撕捍,當(dāng)函數(shù)執(zhí)行時拿穴,就生成了它的執(zhí)行環(huán)境,執(zhí)行上下文會生成函數(shù)的作用域忧风。

除了函數(shù)有執(zhí)行環(huán)境默色,還有全局的環(huán)境。在JS中狮腿,往往不止一個執(zhí)行環(huán)境腿宰。

讓我們先來看一個栗子

var a=10;

function foo(){

? ? var b=5;

? function fn(){

? ? ? var c=20;

? ? ? var d=100;

? }

? fn();

}

foo();

在這個栗子中呕诉,包括了三個執(zhí)行環(huán)境:全局環(huán)境,foo()執(zhí)行環(huán)境吃度,fn()執(zhí)行環(huán)境甩挫;

執(zhí)行環(huán)境的處理機(jī)制

在這里我們要了解到執(zhí)行上下文的第一個特點(diǎn):內(nèi)部的環(huán)境可以訪問外部的環(huán)境,而外部的環(huán)境無法訪問內(nèi)部的環(huán)境椿每。

例如:我們可以在fn()中訪問到位于foo()中的b伊者,在全局環(huán)境中的a,而在foo()中卻無法訪問到c或者d间护。

為什么會這樣亦渗,這就要了解JS處理代碼的一個機(jī)制了。

我們知道JS的處理過程是以堆棧的方式來處理兑牡,JS引擎會把執(zhí)行環(huán)境一個個放入棧里央碟,然后先放進(jìn)去的后處理,后放進(jìn)去的先處理均函,上面這個栗子亿虽,最先被放進(jìn)棧中的是全局環(huán)境,然后是foo()苞也,再是fn()洛勉,然后處理完一個拿出一個來,所以我們知道為什么foo()不能訪問fn()里的了如迟,因?yàn)樗呀?jīng)走了收毫。

執(zhí)行環(huán)境的生命周期

好了,了解完執(zhí)行環(huán)境的的處理方式殷勘,我們要說明執(zhí)行環(huán)境的生命周期此再。執(zhí)行環(huán)境的生命周期分為兩個階段,這兩個階段描述了執(zhí)行環(huán)境在棧里面做了些什么玲销。

創(chuàng)建階段输拇;執(zhí)行階段

創(chuàng)建階段

執(zhí)行環(huán)境在創(chuàng)建階段會完成這么幾個任務(wù):1.生成變量對象;2.建立作用域鏈贤斜;3.確定this指向

執(zhí)行階段

到了執(zhí)行階段策吠,會給變量賦值,函數(shù)引用瘩绒,然后還有執(zhí)行其他的代碼猴抹。

完成了這兩個步驟,執(zhí)行環(huán)境就可以準(zhǔn)備出棧锁荔,一路走好了蟀给。

以上就是執(zhí)行環(huán)境的具體執(zhí)行內(nèi)容。上面提到了執(zhí)行環(huán)境在創(chuàng)建階段會生成變量對象,這也是一個很重要的概念坤溃,我們下文會詳細(xì)論述拍霜。

變量對象(variable object)

變量對象是什么呢?《JS高程》是這樣說的:“每個執(zhí)行環(huán)境都有與之關(guān)聯(lián)的變量對象薪介,環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中≡阶ぃ”

那變量對象里有些什么東西呢汁政?看下文:

變量對象的內(nèi)容

在變量對象創(chuàng)建時,經(jīng)過了這樣三個步驟:

生成arguments屬性缀旁;找到function函數(shù)聲明记劈,創(chuàng)建屬性;找到var變量聲明并巍,創(chuàng)建屬性

其中值得注意的是:function函數(shù)聲明的級別比var變量聲明的級別要高目木,所以在實(shí)際執(zhí)行的過程中會先尋找function的聲明。

還需要注意的是:在執(zhí)行環(huán)境的執(zhí)行階段之前懊渡,變量對象中的屬性都無法訪問刽射,這里還有一個活動對象(activation object)的概念,其實(shí)這個概念正是由進(jìn)入執(zhí)行階段的變量對象轉(zhuǎn)化而來剃执。

來看一個栗子:

function foo(){

? ? var a=10;

? function fn(){

? return5;

? }?

}

foo();

讓我們來看看foo()函數(shù)的執(zhí)行環(huán)境:

它會包括三個部分:1.變量對象誓禁;2.作用域鏈;3.this指向?qū)ο?/p>

創(chuàng)建階段:

建立arguments找到fn()肾档;找到變量a摹恰,undefined;

執(zhí)行階段:

變量對象變成活動對象怒见;arguments還是它~fn();a=10;

以上就是變量對象的內(nèi)容了俗慈,需要記住這個東西,因?yàn)闀奖阄覀兞私庀挛牧硪粋€重要的概念:作用域鏈遣耍。

作用域鏈(scope chain)

什么是作用域鏈闺阱?《JS高程》里的文字是:“作用域鏈的用途,是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問配阵×笏蹋”懵不懵逼?反正我第一次看到的時候確實(shí)是懵逼了棋傍。前面我們說過作用域救拉,那么作用域鏈?zhǔn)遣皇蔷褪谴谝黄鸬淖饔糜蚰兀坎⒉皇恰?/p>

作用域和作用域鏈的關(guān)系瘫拣,作用域是一套通過標(biāo)識符查找變量的規(guī)則亿絮。而作用域鏈則是這套規(guī)則這套規(guī)則的具體運(yùn)行。

是不是還是有點(diǎn)懵逼?還是看例子吧:

function foo(){

? ? ?var a=10;

? function fn(){

? ? return5;

? }

}

foo();

我們還是用上面的栗子派昧,這次我們只看作用域鏈黔姜,根據(jù)規(guī)則,在一個函數(shù)的執(zhí)行環(huán)境的作用域鏈上蒂萎,會依次放入自己的變量對象秆吵,父級的變量對象,祖級的變量對象…一直到全局的變量對象五慈。

比如上面這個栗子纳寂,fn()的執(zhí)行環(huán)境的作用域鏈上會有些什么呢?首先是自己的OV泻拦,然后是foo()的OV毙芜,接著就是全局的OV。而foo()的作用域鏈則會少一個fn()的OV争拐。(OV是變量對象的縮寫)

那這樣放有什么好處呢腋粥?我們知道“作用域鏈保證了當(dāng)前執(zhí)行環(huán)境對符合訪問權(quán)限的變量和函數(shù)的有序訪問〖懿埽”有序隘冲!外層函數(shù)不能訪問內(nèi)層函數(shù)的變量,而內(nèi)層能夠訪問外層音瓷。正是有了這個作用域鏈对嚼,通過這個有方向的鏈,我們可以查找標(biāo)識符绳慎,進(jìn)而找到變量纵竖,才能實(shí)現(xiàn)這個特性。

閉包

好了杏愤,終于要講到這個前端小萌新眼里的小boss了靡砌。在技術(shù)博客和書里翻滾了將將一周,對閉包的各種解釋把我搞得精力憔悴珊楼,懷疑人生通殃。以至于在寫下這段關(guān)于閉包的論述時,也是內(nèi)心忐忑厕宗,因?yàn)槲乙膊淮_定我說的是百分之百正確画舌。

先看看《JS高程》說的:“閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)∫崖”

說法是:“當(dāng)函數(shù)可以記住并訪問所在的作用域(全局作用域除外)時曲聂,就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前作用域之外執(zhí)行佑惠∨笠福”

好吧其實(shí)我覺得都說的不是太清楚齐疙。讓我們這樣來理解,就是內(nèi)部函數(shù)引用了外部函數(shù)的變量對象時旭咽,外部函數(shù)就是一個閉包贞奋。

還是看例子吧。

function foo(){

? ? var a=20;

? ? return

? ? function(){

? ? ? return a;

? ? }

}

foo()();

在這個栗子中穷绵,foo()函數(shù)內(nèi)部返回了一個匿名函數(shù)轿塔,而匿名函數(shù)內(nèi)部引用了外部函數(shù)foo()的變量a,由于作用域鏈请垛,這個引用是有效的催训,按照J(rèn)S的機(jī)制,foo()執(zhí)行完畢后宗收,執(zhí)行環(huán)境會失去引用,內(nèi)存會銷毀亚兄,但是由于內(nèi)部的匿名函數(shù)的引用混稽,a會被暫時保存下來,罩著a的就是閉包审胚。

return一個匿名函數(shù)時創(chuàng)造一個閉包的最簡單的方式匈勋,實(shí)際上創(chuàng)造閉包十分靈活,再看一個栗子:

var fn =null;function foo(){

? ? var a =2;

? ? function innnerFoo(){

? ? ? console.log(a);

? }

? ?fn = innnerFoo;

}

function bar(){

? fn();

}

foo();

bar();// 2

如上膳叨,可以看到:通過把innnerFoo()賦值給全局變量fn洽洁,內(nèi)部的函數(shù)在當(dāng)前作用域外執(zhí)行了,但是這不會影響foo形成了一個閉包菲嘴。

閉包和兩個不同的案例

這兩組栗子都是在各種書籍和各種博客上司空見慣了的栗子饿自,其實(shí)跟閉包的關(guān)系不是很大,但是涉及到了函數(shù)相關(guān)的知識點(diǎn)龄坪,所以在這里寫下來昭雌。也算是積累。

閉包和變量(見《JS高程》P181)

一個例子

function createFunction(){

? ? var result=newArray();

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

? ? ? result[i]=function(){

? ? ? return i;

? ? ?}

?}

return result;

}

alert(createFunction());

這個例子并不會如我們以為的返回從0到9的一串索引值健田。當(dāng)我們執(zhí)行createFunction()時烛卧,函數(shù)內(nèi)會return result,而我們注意到result是一個數(shù)組妓局,而每一個result[i]呢总放?它返回的則是一個函數(shù),而不是這個函數(shù)的執(zhí)行結(jié)果 i好爬。

所以我們想要返回一串索引值的時候局雄,試著選擇result數(shù)組的其中一個,再加上圓括號讓它執(zhí)行起來抵拘,像這樣:

createFunction()[2]()

這樣子就能執(zhí)行了嗎哎榴?運(yùn)行起來發(fā)現(xiàn)并沒有型豁,執(zhí)行的結(jié)果是一串的i,為什么呢尚蝌?

原因是在執(zhí)行createFunction()的時候迎变,i的值已經(jīng)增加到了10,即退出循環(huán)的值飘言,而再要執(zhí)行result內(nèi)部的匿名函數(shù)時衣形,它能獲取到的i就只有10了,所以不管引用多少次姿鸿,i的值都會是10谆吴;

那要如何修改才能達(dá)到我們的目的呢?

function createFunction(){

? ? ? var result=[];

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

? ? ? result[i]=function(num){

? ? ? ? returnfunction(){

? ? ? ? return num;

? ? ?};

? }(i);

}

return result;

}

alert(createFunction()[2]());

彈出的警告和索引值一模一樣苛预。這又是什么原因呢句狼?

我們執(zhí)行

createFunction()

時,把外部的匿名函數(shù)的執(zhí)行結(jié)果賦值給了result热某,返回的result就是十個函數(shù)的數(shù)組腻菇。

而在這個外部函數(shù)里,有一個參數(shù)num昔馋,由于IIFE(立即執(zhí)行函數(shù))的緣故筹吐,循環(huán)過程中的i被賦值給了一個個的num,前后一共保存了10個num秘遏,為什么能夠保存下來呢丘薛?因?yàn)閮?nèi)部的匿名函數(shù)引用了num。而這外部函數(shù)就是一個閉包

接下來邦危,當(dāng)執(zhí)行

createFunction()[2]()

時實(shí)際上是執(zhí)行這個數(shù)組result的第三項(xiàng)洋侨,即:

function(){

? ?return num;

};

這個函數(shù)。

num值是多少呢铡俐?如前所述凰兑,正是對應(yīng)的i。所以返回的值就能夠達(dá)到我們的預(yù)期了审丘。

實(shí)際上吏够,我認(rèn)為這個例子中更重要的是自執(zhí)行函數(shù)這個概念,正是有了自執(zhí)行滩报,才能形成多對對多的引用锅知,盡管這個例子里確實(shí)存在閉包,不過我認(rèn)為用這個例子來介紹閉包并不是太恰當(dāng)脓钾。

閉包和this

this也是JS里一個重中之重售睹。我們知道,JS的this十分靈活的可训,前面已經(jīng)介紹過昌妹,this的指向在函數(shù)執(zhí)行環(huán)境建立時確定捶枢。函數(shù)中的this的指向是一個萌新們的難點(diǎn),什么時候它是指向全局環(huán)境呢飞崖?什么時候它又是指向?qū)ο竽乩檬澹孔⒁猓捍颂幱懻摰氖侵负瘮?shù)中的this,全局環(huán)境下的this一般情況指向window固歪。

結(jié)論一:this的指向是在函數(shù)被調(diào)用的時候確定的

因?yàn)楫?dāng)一個函數(shù)調(diào)用時蒜鸡,一個執(zhí)行環(huán)境就創(chuàng)建了,接著它會執(zhí)行牢裳,這是執(zhí)行環(huán)境的生命周期逢防。所以this的指向是在函數(shù)被調(diào)用時確定的。

結(jié)論二:當(dāng)函數(shù)執(zhí)行時蒲讯,如果這個函數(shù)是屬于某個對象忘朝,調(diào)用的方式是以對象的方法進(jìn)行的,那么this的指向就是這個對象判帮,而其他情況辜伟,如函數(shù)獨(dú)立調(diào)用,則基本是指向全局對象脊另。

PS:實(shí)際上這個說法不大準(zhǔn)確,當(dāng)函數(shù)獨(dú)立調(diào)用時约巷,在嚴(yán)格模式下偎痛,this的指向時undefined,而非嚴(yán)格模式下独郎,則時指向全局對象踩麦。

為了更好的說明,讓我們看一個例子:

var a =20;

var foo ={

? a:10,

? getA:function(){

? ? returnthis.a;

? }

}

console.log(foo.getA());// 10

var test = foo.getA;

console.log(test());// 20

在上面這個例子中氓癌,foo.getA()作為對象方法的調(diào)用谓谦,指向的自然是這個對象,而test雖然指向和foo.getA相同贪婉,但是因?yàn)槭仟?dú)立調(diào)用反粥,所以在非嚴(yán)格模式下,指向的是全局對象疲迂。

除了上面的例子才顿,在《JS高程》中還有一個經(jīng)典的例子,眾多博客文章均有討論尤蒿,但是看過之后覺得解釋還是不夠清楚郑气,至少我沒完全理解,這里我將試著用自己的語言來解釋腰池。

var name="the window";

varobject={

? name:"my object",

? getNameFunc:function(){

? ? returnfunction(){

? ? ? returnthis.name;

? ? };

? }

};

alert(object.getNameFunc()());// the window

在這個帶有閉包的例子里尾组,我們可以看到object.getNameFunc()執(zhí)行的返回是一個函數(shù)忙芒,再加()執(zhí)行則是一個直接調(diào)用了。所以指向的是全局對象讳侨。

如果我們想要返回變量對象怎么辦呢呵萨?

讓我們看一段代碼:

var name=“the window”;

varobject={

? name:"my object",getFunc:function(){

? ? returnthis.name;

}};

alert(object.getFunc());//"my object"```

我去掉了上面例子的閉包,可以看出在方法調(diào)用的情況下爷耀,this指向的是對象甘桑,那么我們只要在閉包能訪問到的位置,同時也是在這個方法調(diào)用的同一個作用域里設(shè)置一個“中轉(zhuǎn)站”就好了歹叮,讓我們把這個位置的this賦值給一個變量來存儲跑杭,然后匿名函數(shù)調(diào)用這個變量時指向的就會是對象而不是全局對象了。

var name="the window";

varobject={

? name:"my object",

? ? getFunc:function(){

? ? var that=this;

? ? ? returnfunction(){

? ? ? ? return that;

? ? };

? }

};

alert(object.getFunc());

that’s all

閉包的應(yīng)用

閉包的應(yīng)用太多了咆耿,最重要的一個就是模塊模式了德谅。不過說實(shí)話,實(shí)在還沒上路萨螺,所以這里就用一個模塊的栗子來結(jié)尾吧窄做。(強(qiáng)行結(jié)尾)

(function(){

? ? var a =10;

? ? var b =20;

? ? ? function add(num1, num2){

? ? ? ? var num1 =!!num1 ? num1 : a;

? ? ? ?var num2 =!!num2 ? num2 : b;

? ? ? return num1 + num2;

? ? }

? ? window.add = add;

})();

add(10,20);

我們需要知道的是,所謂模塊利用的就是閉包外部無法訪問內(nèi)部慰技,內(nèi)部卻能訪問外部的特性椭盏,通過引用了指定的公共變量和方法,達(dá)到訪問私有變量和方法的目的吻商。模塊可以保證模塊內(nèi)部的私有方法和變量不被外部變量污染掏颊,進(jìn)而方便更大規(guī)模的開發(fā)項(xiàng)目。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末艾帐,一起剝皮案震驚了整個濱河市乌叶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柒爸,老刑警劉巖准浴,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捎稚,居然都是意外死亡乐横,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門阳藻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晰奖,“玉大人,你說我怎么就攤上這事腥泥∝夷希” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵蛔外,是天一觀的道長蛆楞。 經(jīng)常有香客問我溯乒,道長,這世上最難降的妖魔是什么豹爹? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任裆悄,我火速辦了婚禮,結(jié)果婚禮上臂聋,老公的妹妹穿的比我還像新娘光稼。我一直安慰自己,他們只是感情好艾君,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肄方,像睡著了一般冰垄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上权她,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天虹茶,我揣著相機(jī)與錄音,去河邊找鬼隅要。 笑死蝴罪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的步清。 我是一名探鬼主播洲炊,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼尼啡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起询微,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤崖瞭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后撑毛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體书聚,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年藻雌,在試婚紗的時候發(fā)現(xiàn)自己被綠了雌续。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡胯杭,死狀恐怖驯杜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情做个,我是刑警寧澤鸽心,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布滚局,位于F島的核電站,受9級特大地震影響顽频,放射性物質(zhì)發(fā)生泄漏藤肢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一糯景、第九天 我趴在偏房一處隱蔽的房頂上張望嘁圈。 院中可真熱鬧,春花似錦蟀淮、人聲如沸最住。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽温学。三九已至,卻和暖如春甚疟,著一層夾襖步出監(jiān)牢的瞬間仗岖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工览妖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轧拄,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓讽膏,卻偏偏與公主長得像檩电,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子府树,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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