欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

深入作用域鏈與閉包

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

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

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

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

作用域鏈

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

[[Environment]]

所以其實(shí)在函數(shù)被聲明的時候,就被加上了一個內(nèi)部屬性[[Environment]],根據(jù)規(guī)范定義,它也是一個環(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ù)執(zhí)行的時候,創(chuàng)建的詞法環(huán)境變量環(huán)境都是存儲在 [[Environment]] 中的

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

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

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

閉包

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

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

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

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

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

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

什么是閉包

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

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

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

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

變量綁定

為什么要提到綁定?

  • 當(dāng)外部變量發(fā)生變化時,閉包中的對應(yīng)的變量也會發(fā)生變化。
  • 在閉包中的使外部變量發(fā)生變化,其綁定的環(huán)境記錄中的變量也會變化。
?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 也對應(yīng)+1)

這個綁定也可以解釋一個經(jīng)典的面試題(相關(guān)前置知識可以參考上一節(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 聲明的,所以會“逸出”保存到 foo 的變量環(huán)境中。因此setTimeout 中的匿名函數(shù)閉包中的 i 是與 foo 環(huán)境所綁定。當(dāng)執(zhí)行 i++ ,即 foo 的 i++ ,閉包中的 i 隨之變化。因?yàn)樗虚]包綁定了同一個環(huán)境記錄,所以是顯示同一個值,退出循環(huán)后仍然執(zhí)行了一次 i++,因此輸出為 6 而不是 5。

對應(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án)境中,即的詞法環(huán)境。每次循環(huán)都會形成一個新的塊級作用域,因此 i 保存的環(huán)境都不一樣,即每個setTimeout 匿名函數(shù)閉包中的 i 綁定了不同的環(huán)境記錄。因此可以單獨(dú)管理。

同一個閉包

上文提到,在同一個函數(shù)實(shí)例中,所有子函數(shù)公用一個閉包。我們用具體代碼來驗(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();

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

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

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

這里我們調(diào)用fun1.addNum 修改了 d 的值,但是實(shí)例 fun2 的閉包中的 d 仍然是 4 ??梢钥闯鰞蓚€閉包是獨(dú)立的。

總結(jié)

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

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

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

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

?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è)每個空間都有一個的堆內(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)境,如果有閉包則會指向上一層函數(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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論