亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript深入理解作用域鏈與閉包詳情

 更新時(shí)間:2022年07月15日 16:22:51   作者:??四霉?  
這篇文章主要介紹了JavaScript深入理解作用域鏈與閉包詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下

深入作用域鏈與閉包

為什么要把作用域鏈和閉包放在一起講呢,它們有什么關(guān)聯(lián)嗎?

試想,我們?nèi)绻谝粋€(gè)內(nèi)部的函數(shù)使用了外部的變量,是通過[[outerEnv]]串起來的詞法環(huán)境各類環(huán)境記錄),即最終在瀏覽器上的實(shí)現(xiàn),作用域鏈[[Scope]]。

而閉包的觸發(fā),是需要在一個(gè)獨(dú)立的空間中管理從外部獲得的變量。而這個(gè)外部變量的獲取與綁定,則是需要通過作用域鏈。

所以理解了作用域鏈的形成原理,才能更好的深入理解閉包。

作用域鏈

上節(jié)的例子中對(duì)于函數(shù)中變量記錄的闡釋并不完備,只是簡(jiǎn)單的將VariableEnvironemnt.[[outerEnv]] 指向了外部。仔細(xì)思考的同學(xué)可能會(huì)發(fā)現(xiàn),JavaScript里面萬物皆對(duì)象,函數(shù)這個(gè)對(duì)象滿天飛,如果每次都要解析全局詞法來獲取某個(gè)函數(shù)的外部環(huán)境,是不是很浪費(fèi)性能呢?

[[Environment]]

所以其實(shí)在函數(shù)被聲明的時(shí)候,就被加上了一個(gè)內(nèi)部屬性[[Environment]],根據(jù)規(guī)范定義,它也是一個(gè)環(huán)境記錄,其[[outerEnv]] 指向聲明函數(shù)的詞法環(huán)境。

10.2 ECMAScript Function Objects) Internal Slots of ECMAScript Function Objects

Internal SlotTypeDescription
[[Environment]]an Environment RecordThe Environment Record that the function was closed over. Used as the outer environment when evaluating the code of the function.

完善環(huán)境記錄

同時(shí),在函數(shù)執(zhí)行的時(shí)候,創(chuàng)建的詞法環(huán)境變量環(huán)境都是存儲(chǔ)在 [[Environment]] 中的

?function foo() {
? ? ?var a = 1;
? ? ?let b = 2;
?}
?foo();

在函數(shù)執(zhí)行前創(chuàng)建上下文時(shí),較為完備的解釋應(yīng)該如下:

?ExecutionContext: {
? ?  [[Environment]](0x00): {
? ? ? ? ?LexicalEnvironment(0x01): {
? ? ? ? ? ? ?b -> nothing
? ? ? ? ? ?  [[outerEnv]]: 0x02
? ? ? ?  }
? ? ? ? ?VariableEnvironment(0x02): {
? ? ? ? ? ? ?a -> undefined
? ? ? ? ? ?  [[outerEnv]]: 0x00
? ? ? ?  }
? ? ? ? ?...
? ? ? ?  [[outerEnv]]: global
? ?  }
?}

閉包

函數(shù)實(shí)例

為了更好的解釋閉包。先了解一下函數(shù)實(shí)例化的概念:聲明函數(shù)的時(shí)候可以使用new Function,實(shí)例出來一個(gè)函數(shù)對(duì)象

  • 函數(shù)聲明:可以叫做函數(shù)實(shí)例化,創(chuàng)建了原型 Function 的一個(gè)實(shí)例
  • 函數(shù)表達(dá)式:則為創(chuàng)建函數(shù)的實(shí)例

舉個(gè)例子說明同一個(gè)函數(shù)代碼塊能有多個(gè)函數(shù)實(shí)例:

?function foo() {
?    return function myFun() {}
?}
?const fun1 = foo()
?const fun2 = foo()
?console.log(fun1 === fun2) // false

在這里,myFun 就是一個(gè)函數(shù)表達(dá)式,而fun1/fun2 就是兩個(gè)不同的實(shí)例

什么是閉包

基于這個(gè)概念,關(guān)于作用域鏈與閉包的關(guān)系可以這么理解:

每生成一個(gè)函數(shù)實(shí)例,實(shí)例內(nèi)部都會(huì)有一條由環(huán)境記錄(包括函數(shù)自身的)串成的作用域鏈。而閉包可以理解為是與函數(shù)實(shí)例的作用域鏈綁定的一個(gè)映像。

在具體實(shí)踐中(如V8引擎),函數(shù)在預(yù)編譯的時(shí)候會(huì)解析函數(shù)內(nèi)部的詞法,無論深度、子函數(shù)是否被調(diào)用,只要內(nèi)部有用到外部的變量,就會(huì)把它們存到同一個(gè)閉包上,由于這些變量是通過作用域鏈獲取且綁定的,所以可以說閉包只是一個(gè)作用域鏈的丐版復(fù)制品

同時(shí),這個(gè)閉包可以理解為父函數(shù)的一個(gè)屬性,且同一個(gè)實(shí)例中的所有子函數(shù)使用同一個(gè)閉包,后文會(huì)對(duì)這一點(diǎn)進(jìn)行驗(yàn)證。

變量綁定

為什么要提到綁定?

  • 當(dāng)外部變量發(fā)生變化時(shí),閉包中的對(duì)應(yīng)的變量也會(huì)發(fā)生變化。
  • 在閉包中的使外部變量發(fā)生變化,其綁定的環(huán)境記錄中的變量也會(huì)變化。
?let a = 1;
?let b = 2;
?function foo() {
? ?return function () {
? ? ?a += 1;
? ? ?b += 10;
? ? ?console.log(a, b);
?  };
?}
?const bar = foo();
?bar();  // 2 12
?bar();  // 3 22
?a += 10;
?bar();  // 14 32
?a = 0;
?bar();  // 1 42
?cnsole.log(a) // 1 (在閉包中+1,全局環(huán)境中的 a 也對(duì)應(yīng)+1)

這個(gè)綁定也可以解釋一個(gè)經(jīng)典的面試題(相關(guān)前置知識(shí)可以參考上一節(jié)《環(huán)境變量》)

?function foo() {
? ?for (var i = 0; i < 6; i++) {
? ? ?setTimeout(() => {
? ? ? ?console.log(i);
? ?  }, i * 100);
?  }
?}
?foo();  // 6 6 6 6 6

因?yàn)?nbsp;i 是使用 var 聲明的,所以會(huì)“逸出”保存到 foo 的變量環(huán)境中。因此setTimeout 中的匿名函數(shù)閉包中的 i 是與 foo 環(huán)境所綁定。當(dāng)執(zhí)行 i++ ,即 foo 的 i++ ,閉包中的 i 隨之變化。因?yàn)樗虚]包綁定了同一個(gè)環(huán)境記錄,所以是顯示同一個(gè)值,退出循環(huán)后仍然執(zhí)行了一次 i++,因此輸出為 6 而不是 5。

對(duì)應(yīng)的。我們來看看用 let 聲明的 i 的表現(xiàn)。

?function foo() {
? ?for (let i = 0; i < 6; i++) {
? ? ?setTimeout(() => {
? ? ? ?console.log(i);
? ?  }, i * 100);
?  }
?}?
?foo();  // 1 2 3 4 5

這里的 i 是由 let 聲明,所以它會(huì)被保存到最近的詞法環(huán)境中,即的詞法環(huán)境。每次循環(huán)都會(huì)形成一個(gè)新的塊級(jí)作用域,因此 i 保存的環(huán)境都不一樣,即每個(gè)setTimeout 匿名函數(shù)閉包中的 i 綁定了不同的環(huán)境記錄。因此可以單獨(dú)管理。

同一個(gè)閉包

上文提到,在同一個(gè)函數(shù)實(shí)例中,所有子函數(shù)公用一個(gè)閉包。我們用具體代碼來驗(yàn)證一下

?function foo() {
? ?let a = 1;
? ?const b = 2;
? ?let c = 3;
? ?let d = 4;
? ?function bar() {  // 驗(yàn)證深度以及沒有被調(diào)用的情況
? ? ?console.log(a);
? ? ?function barSon() { 
? ? ? ?console.log(b);
? ?  }
?  }
? ?return function () {
? ? ?console.log(d);
? ? ?return {
? ? ? ?addNum() {
? ? ? ? ?d = "new" + d;
? ? ?  },
? ?  };
?  };
?}
?const fun1 = foo();
?fun1().addNum();    // 驗(yàn)證不同實(shí)例的閉包空間獨(dú)立
?fun1();

?const fun2 = foo();
?fun2();

這里我們新建了兩個(gè)實(shí)例,按照上文的理論,二者的閉包應(yīng)該是獨(dú)立的,且所有子函數(shù)無論深度以及子函數(shù)是否被調(diào)用都會(huì)共用一個(gè)閉包。

上圖的包含變量 a,b 的子函數(shù)并沒有調(diào)用,但是在閉包中仍然存在。

返回的匿名函數(shù)和 bar 有使用到的變量在同一個(gè)閉包 foo.Closure 中

這里我們調(diào)用fun1.addNum 修改了 d 的值,但是實(shí)例 fun2 的閉包中的 d 仍然是 4 。可以看出兩個(gè)閉包是獨(dú)立的。

總結(jié)

與靜態(tài)的函數(shù)實(shí)例相對(duì)應(yīng),閉包是一個(gè)運(yùn)行期的概念,其在函數(shù)執(zhí)行的過程中處于激活的、可訪問的狀態(tài)。并在函數(shù)實(shí)例調(diào)用結(jié)束后保持?jǐn)?shù)據(jù)信息的最終狀態(tài),直到閉包被銷毀。(具體體現(xiàn)形式為上個(gè)例子中不斷調(diào)用同一個(gè)函數(shù)會(huì)輸出不同而值,而這些值是來自于上次調(diào)用的最終數(shù)據(jù)狀態(tài))

實(shí)際上,函數(shù)執(zhí)行時(shí),如果當(dāng)前函數(shù)有使用到外部變量,會(huì)新建一個(gè)環(huán)境記錄作為閉包(規(guī)范定義),它存在與 [[Environment]].[[outerEnv]] 的可變綁定。(在瀏覽器中,只要函數(shù)內(nèi)部對(duì)外部變量進(jìn)行引用,函數(shù)的內(nèi)部屬性[[Scope]]中會(huì)有名為 Closure 的閉包)

下面是上一個(gè)例子執(zhí)行完畢之后兩個(gè)函數(shù)實(shí)例的[[Scope]]

在最后舉個(gè)例子形象說明一下閉包在作用域鏈中處于什么位置

?function foo() {
? ?let a = 1;
? ?const b = 2;
? ?let c = 3;
? ?let d = 4;
? ?function bar() {  // 驗(yàn)證深度以及沒有被調(diào)用的情況
? ? ?console.log(a);
? ? ?function barSon() { 
? ? ? ?console.log(b);
? ?  }
?  }
? ?return function myFun() {
? ? ?console.log(d);
?  };
?}?
?const fun1 = foo();
?// 詞法編譯時(shí),假設(shè)每個(gè)空間都有一個(gè)的堆內(nèi)存
?ExecutionContext(foo): {
? ?  [[outerEnv]]: global
?    ...
? ?  [[Environment]](0x00): {
?        LexicalEnviroemnt(0x01): {
? ? ? ? ? ?  [[outerEnv]]: 0x00
?            ...
? ? ? ? ? ? ?a —> nothing, b -> nothing, c -> nothing, d -> nothing
? ? ? ? ? ? ?bar: {
? ? ? ? ? ? ? ? ?LexicalEnviroemnt(0x02): {...}
? ? ? ? ? ? ? ?  [[Environment]] : {
? ? ? ? ? ? ? ? ? ?  [[outerEnv]] : 0x10 // 指向閉包
? ? ? ? ? ? ? ? ? ? ?...
? ? ? ? ? ? ? ?  }
? ? ? ? ? ? ? ? ?barSon: {
? ? ? ? ? ? ? ? ? ?  [[Environment]] : {
? ? ? ? ? ? ? ? ? ? ? ? ?// 指向上一層函數(shù)的詞法環(huán)境,如果有閉包則會(huì)指向上一層函數(shù)的閉包
? ? ? ? ? ? ? ? ? ? ? ?  [[outerEnv]] : 0x02
? ? ? ? ? ? ? ? ? ? ? ? ?...
? ? ? ? ? ? ? ? ? ?  }
? ? ? ? ? ? ? ?  }
? ? ? ? ? ?  }
?            myFun: {
? ? ? ? ? ? ? ?  [[Environment]] : {
? ? ? ? ? ? ? ? ? ?  [[outerEnv]] : 0x10 // 指向閉包
? ? ? ? ? ? ? ? ? ? ?...
? ? ? ? ? ? ? ?  }
? ? ? ? ? ?  }
? ? ? ?  }
?        // 即所謂的閉包,這里面變量的來源于外部的環(huán)境記錄(某種映射)
?        EnvironmentRecord(0x10): {
?            a -> nothing, b -> nothing, d -> nothing
? ? ? ? ? ?  [[outerEnv]]: 0x01  // 指向外部詞法環(huán)境
? ? ? ?  }
? ?  }
? ? ?...
?}

到此這篇關(guān)于JavaScript深入理解作用域鏈與閉包詳情的文章就介紹到這了,更多相關(guān)JS作用域鏈與閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論