[英]Functions, arrow functions, closures and ExecutionContext
我試圖了解 JavaScript 中的箭頭函數,並且有一些關於它們如何與 ExecutionContext/environment 和閉包交互的問題。
據我所知,JS 中的“規范”model 是當代碼被執行時,會維護一個ExecutionContext
堆棧( 1 、 6 )。 即在開始時有一個全局的ExecutionContext
,當 function 被調用時,一個新的ExecutionContext
被添加到它的執行時間,當它完成時,它被彈出。 即它匹配callstack 上的幀。
假設稍微簡化一下(忽略 global/function/eval & no let
和const
(即variable environment
)之間的差異), ExecutionContext
由LexicalEnvironemnt
組成,而 LexicalEnvironemnt 又由三個組件組成:
ExecutionContext
的引用this
變量引用的內容。 對於未綁定函數,這是根據調用方法的方式設置的 ( 2 ) 當調用 function 時,會在其執行期間創建一個新的ExecutionContext
(以跟蹤其變量在環境記錄中的變化,...)。
對於正常的 function,下面示例中的 sa b()
,新ExecutionContext
的創建相對簡單。
function a() {
var myVar = 42;
function b() {
console.log(myVar)
console.log(this)
}
b()
}
a()
a()
)在執行堆棧 ( 3 ) 上的EnvironmentContext
。 這使我們能夠訪問外部詞法 scope 變量myVar
。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()
)。
這個捕獲的閉包是可見的?偽? returnedFun
的console.dir
時的屬性 object ( .[[Scopes]][0].myVar == 42
)。
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 ),而是重新使用它們的詞法外部范圍之一; 但它是如何工作的,真的嗎?
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)()
但是如果箭頭 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
或正常閉包的方式相同。
ExecutionContext
的引用,特別是箭頭函數的this 綁定,是否使用與閉包類似的機制(在類似模型下)存儲(想想[[scopes]]
正如 chrome 開發工具顯示的那樣)是?bind(...)
創建的thisBinding
和正常的閉包都在 via Chrome devtools/ console.dir
中可見,但箭頭函數的this 綁定不是? 它只是實現細節還是有一些更高層次的原因?bind
的函數和箭頭函數看起來有差異(或者它只是實現細節而不是 JS 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.