[英]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.