簡體   English   中英

函數、箭頭函數、閉包和 ExecutionContext

[英]Functions, arrow functions, closures and ExecutionContext

我試圖了解 JavaScript 中的箭頭函數,並且有一些關於它們如何與 ExecutionContext/environment 和閉包交互的問題。

我如何理解model:

據我所知,JS 中的“規范”model 是當代碼被執行時,會維護一個ExecutionContext堆棧( 16 )。 即在開始時有一個全局的ExecutionContext ,當 function 被調用時,一個新的ExecutionContext被添加到它的執行時間,當它完成時,它被彈出。 即它匹配callstack 上的幀

假設稍微簡化一下(忽略 global/function/eval & no letconst (即variable environment )之間的差異), ExecutionContextLexicalEnvironemnt組成,而 LexicalEnvironemnt 又由三個組件組成:

  1. 環境記錄:變量/函數符號和它們所代表的對象之間的映射。
  2. 對外部環境的引用:對詞法外部ExecutionContext的引用
  3. 此綁定this變量引用的內容。 對於未綁定函數,這是根據調用方法的方式設置的 ( 2 )

當調用 function 時,會在其執行期間創建一個新的ExecutionContext (以跟蹤其變量在環境記錄中的變化,...)。

正常功能

正常 function,在詞法 scope 內:

對於正常的 function,下面示例中的 sa b() ,新ExecutionContext的創建相對簡單。

function a() {
 var myVar = 42;
 function b() {
   console.log(myVar)
   console.log(this)
 }
 b()
}

a()
  1. 環境記錄:對所有類型總是很簡單,掃描方法,記下所有符號,初始化為默認值即可。
  2. 對外部環境的引用:我們在其詞法外部 scope 中運行該方法,即我們可以簡單地引用當前(即a() )在執行堆棧 ( 3 ) 上的EnvironmentContext 這使我們能夠訪問外部詞法 scope 變量myVar
  3. 它被正常調用,所以我們將 go 與全局綁定,即在瀏覽器中為 window。
正常 function,外部詞匯 scope:
function a() {
 let myVar = 42;
 function b() {
   console.log(myVar) // from closure
   console.log(myCVar) // will not be accessible, even if it will have lived in above frame (from c)
   console.log(this)
 }
 
 return b
}

function c(f) {
 let myVar = 48;
 let myCVar = 49;
 f()
}

returnedFun = a()
c(returnedFun)

在這種情況下,當我們運行方法b (作為方法c中的f() ,從a返回之后),事情就沒那么簡單了。 1)3)ExecutionContext的部分仍然填充相同,但2)必須不同。

b從其詞法 scope 返回的位置,即從 function a返回時,必須在當前ExecutionContext中創建一個閉包(正在執行a()的閉包,環境記錄中有myVar: 42 )並添加到返回的function object b

當 function object 在 function c ( f() ) 中執行時,不是將新創建的ExecutionContext對外部環境的引用連接到執行堆棧頂部的那個(即當前正在執行的c()的那個),必須使用 function object f的關閉(返回 function b )。

即,剛剛執行的f()的剛剛創建的ExecutionContext對外部環境的引用不指向當前正在運行的 function 的ExecutionContext (即運行時外部 scope;將是c() ),而是指向一個捕獲的閉包不再運行詞法外部環境( a() )。

這個捕獲的閉包是可見的?偽? returnedFunconsole.dir時的屬性 object ( .[[Scopes]][0].myVar == 42 )。

正常function,綁定
let myObj = {asdf: 42}
function a() { console.write("tst");}
console.dir(a.bind(myObj))

同樣,當bind被顯式使用時 - args/this 被添加到 function object,可見為?偽? 屬性[[BoundThis]] 並且在調用 function object 並創建相應的ExecutionContext以填充其This 綁定時使用它。

箭頭函數

但是箭頭函數呢? 根據我的谷歌搜索,解釋它們的一種常見方法是它們沒有獲得自己的ExecutionContext ( 4 , 5 ),而是重新使用它們的詞法外部范圍之一; 但它是如何工作的,真的嗎?

箭頭函數,在詞法 scope 內:
function a() {
 let myVar = 42;
 b = () => {
   var myBVar = 48;
 }
 
 b()
 console.log(myBVar) // not accessible -> run of b() must use copy of a's EC
}
a()

當箭頭 function 在其詞法 scope 中執行時,它再次相對簡單。 當 function b()被執行時,當前的 ExecutionContext(對於a ,這是b的詞法外部范圍)被復制(需要允許只有它自己的變量,否則在a()期間你可以訪問myBVar )並使用; 包括此綁定(由下面的顯式bind示例演示)。

function a() {
  console.log(this)
  arrF = () => {
    console.log(this.myMyObjVar)
  }
  arrF() // when called duplicates current ExecutionContext (LexicalEnvironment + thisBinding), runs in it.
}

var myObj = {myMyObjVar: 42}
a.bind(myObj)()
箭頭函數,詞法外部 scope

但是如果箭頭 function 逃脫了它的詞法 scope 呢? 即它需要創建閉包嗎?

function a() {
  console.log(this)
  var asdf = 48;
  arrF = () => {
    console.log(this.myMyObjVar)
    console.log(asdf)
  }
  
  return arrF
}

var myObj = {myMyObjVar: 42}
aBound = a.bind(myObj)

returnedArrF = aBound()
returnedArrF()
console.dir(returnedArrF)

在這種情況下, returnedArrF的閉包不僅需要包含a()ExecutionContext環境記錄(以提供對外部詞法 scope ( asdf ) 變量的正常閉包訪問),即 Chromium Devtools 向我們展示的[[Scopes]] ,還要對其This 進行綁定 即需要保存幾乎整個ExecutionContext ,以允許 excaped 箭頭 function - 在執行時 - 不需要擁有自己的並重用其外部詞法范圍的箭頭。

奇怪的是,存儲的This 綁定似乎沒有顯示為?偽? 使用console.dir可見的屬性,與bind 'ed this或正常閉包的方式相同。

我有什么問題?

  1. 對外部詞法上下文的ExecutionContext的引用,特別是箭頭函數的this 綁定,是否使用與閉包類似的機制(在類似模型下)存儲(想想[[scopes]]正如 chrome 開發工具顯示的那樣)是?
  • 如果是這樣,為什么bind(...)創建的thisBinding和正常的閉包都在 via Chrome devtools/ console.dir中可見,但箭頭函數的this 綁定不是? 它只是實現細節還是有一些更高層次的原因?
  1. 為什么在檢查時顯式bind的函數和箭頭函數看起來有差異(或者它只是實現細節而不是 JS model 要求的東西?)?
  2. 我的 model 對嗎?
什么不是我的問題/筆記?

我知道ExecutionContext等只是一個規范“模型”,而不是單個 VM(V8,...)如何實現 JS。 我還了解到 Chromium devtools 可能會顯示對象上並不存在/可訪問的“偽”屬性 (sa [[Scopes]] )。

我也對箭頭函數如何表現、如何使用它們不感興趣(我認為我掌握得很好;但如果你認為我根據我的示例遺漏了一些東西 - 請隨時告訴我)。

相反,我很好奇規范“模型”如何映射到實際實現。 我希望從問題中可以清楚地知道。

筆記:

我試圖閱讀以理解這一點的東西:

這個問題很廣泛,並且存在很多混亂,但我希望這篇文章可以提供一些清晰的信息。 這是一個社區 wiki 帖子,任何人都可以編輯。 如果您有后續問題,我們很樂意為您提供幫助。

非箭頭函數

函數的詞法環境是在創建function 時定義的。 當評估者在普通評估中遇到一個 function 時,將創建一個 function。 這對於非箭頭和箭頭函數都是如此。 但是,所有非箭頭函數都有一個動態上下文this ,它會根據 function 的調用方式或bind方式、 call方式或apply方式而變化 -

 function F(...args) { console.log(this.value, ...args) } value = "global" const A = F.bind({value: "hello"}, 1, 2, 3) const B = A.bind({value: "world"}, 4, 5, 6) const o = { value: "object", method: F } F(7,8,9) // global 7 8 9 A(7,8,9) // hello 1 2 3 7 8 9 B(7,8,9) // hello 1 2 3 4 5 6 7 8 9 o.method(7,8,9) // object 7 8 9

箭頭函數

當我們將F更改為箭頭 function 時會發生什么? 箭頭函數有一個無法反彈的詞法this 但為什么?

const F = (...args) => {
  console.log(this.value, ...args)
}

這相當於下面的代碼 -

const LEXICAL_THIS = this
function F(...args) {
   console.log(LEXICAL_THIS.value, ...args)
}

箭頭函數沒有自己的動態上下文,而是從創建它們的環境中繼承this 。而且因為它們沒有自己的動態上下文,所以不存在要bind的上下文。 這是它的行為方式 -

 const F = (...args) => { console.log(this.value, ...args) } value = "global" const A = F.bind({value: "hello"}, 1, 2, 3) const B = A.bind({value: "world"}, 4, 5, 6) const o = { value: "object", method: F } F(7,8,9) // global 7 8 9 A(7,8,9) // global 1 2 3 7 8 9 B(7,8,9) // global 1 2 3 4 5 6 7 8 9 o.method(7,8,9) // global 7 8 9

嵌套函數

If you write a function inside of another function, the inner function is not created until the outer function is executed. 多次執行外部 function 將多次重新創建內部 function (和環境) -

 const add = x => y => x + y const add3 = add(3) const add6 = add(6) console.log(add3(10), add3(20), add3(30)) // 13 23 33 console.log(add6(10), add6(20), add6(30)) // 16 26 36

這是使用非箭頭函數的相同程序 -

 function add(x) { return function(y) { return x + y } } const add3 = add(3) const add6 = add(6) console.log(add3(10), add3(20), add3(30)) // 13 23 33 console.log(add6(10), add6(20), add6(30)) // 16 26 36

TLDR:箭頭函數...與 ExecutionContext/環境和閉包交互,就像代碼塊一樣。

您在這里使事情過於復雜,在我看來,我認為它實際上只是關於this關鍵字,因為它是ExecutionContext中唯一將箭頭函數傳統函數區分開來的東西
一旦你得到了 3 的答案,即箭頭函數的“模型”,問題 1 和 2 就沒有意義了,如果考慮到它們沒有this綁定,這很簡單。
考慮以下示例:

const fn = () => { console.log(this); };

// block code call:
{ console.log(this); }

// arrow function call:
fn();

// arrow function call with `this` value:
fn.call(0);

// arrow function call as method:
const object = {fn};
object.fn();

無論在哪個上下文中執行上述示例,它們都會記錄相同的值。
因此,箭頭 function 的“模型”可以定義為:可以像 function 一樣執行的可重用代碼塊,其中 arguments 充當塊范圍的變量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM