JavaScript闭包如何在低级别工作?
我理解,关闭的定义如下: [A] 函数返回时未释放的堆栈帧。(好像一个“堆栈帧”是malloc'ed而不是在堆栈上!) 但我不明白这个答案如何适用于JavaScript的存储机制。解释器如何跟踪这些值?浏览器的存储机制是否以类似于堆和堆栈的方式分段 关于这个问题的答案是:说明: [A] 函数引用还有一个对闭包的秘密引用 这种神秘的“秘密参照”背后的潜在机制是什么 编辑JavaScript闭包如何在低级别工作?,javascript,browser,closures,Javascript,Browser,Closures,我理解,关闭的定义如下: [A] 函数返回时未释放的堆栈帧。(好像一个“堆栈帧”是malloc'ed而不是在堆栈上!) 但我不明白这个答案如何适用于JavaScript的存储机制。解释器如何跟踪这些值?浏览器的存储机制是否以类似于堆和堆栈的方式分段 关于这个问题的答案是:说明: [A] 函数引用还有一个对闭包的秘密引用 这种神秘的“秘密参照”背后的潜在机制是什么 编辑 很多人都说这取决于实现,因此为了简单起见,请在特定实现的上下文中提供解释 下面是一个示例,说明如何将需要闭包的代码转换为不需要闭
很多人都说这取决于实现,因此为了简单起见,请在特定实现的上下文中提供解释 下面是一个示例,说明如何将需要闭包的代码转换为不需要闭包的代码。需要注意的要点是:如何转换函数声明、如何转换函数调用以及如何转换对已移动到堆中的局部变量的访问 输入:
var f = function (x) {
x = x + 10
var g = function () {
return ++x
}
return g
}
var h = f(3)
console.log(h()) // 14
console.log(h()) // 15
var f = function f(x, y) {
var z = 7
...
}
输出:
// Header that goes at the top of the program:
// A list of environments, starting with the one
// corresponding to the innermost scope.
function Envs(car, cdr) {
this.car = car
this.cdr = cdr
}
Envs.prototype.get = function (k) {
var e = this
while (e) {
if (e.car.get(k)) return e.car.get(k)
e = e.cdr
}
// returns undefined if lookup fails
}
Envs.prototype.set = function (k, v) {
var e = this
while (e) {
if (e.car.get(k)) {
e.car.set(k, v)
return this
}
e = e.cdr
}
throw new ReferenceError()
}
// Initialize the global scope.
var envs = new Envs(new Map(), null)
// We have to use this special function to call our closures.
function call(f, ...args) {
return f.func(f.envs, ...args)
}
// End of header.
var f = {
func: function (envs, x) {
envs = new Envs(new Map().set('x',x), envs)
envs.set('x', envs.get('x') + 10))
var g = {
func: function (envs) {
envs = new Envs(new Map(), envs)
return envs.set('x', envs.get('x') + 1).get('x')
},
envs: envs
}
return g
},
envs: envs
}
var h = call(f, 3)
console.log(call(h)) // 14
console.log(call(h)) // 15
var f = {
func: function f(envs, x, y) {
envs = new Envs(new Map().set('x',x).set('z',7), envs)
...
}
envs: envs
}
让我们分析一下三个关键转换是如何进行的。对于函数声明情况,为具体起见,假设我们有一个函数,它有两个参数x
和y
和一个局部变量z
,并且x
和z
可以逃逸堆栈帧,因此需要移动到堆中。由于提升的原因,我们可以假设z
是在函数的开头声明的
输入:
var f = function (x) {
x = x + 10
var g = function () {
return ++x
}
return g
}
var h = f(3)
console.log(h()) // 14
console.log(h()) // 15
var f = function f(x, y) {
var z = 7
...
}
输出:
// Header that goes at the top of the program:
// A list of environments, starting with the one
// corresponding to the innermost scope.
function Envs(car, cdr) {
this.car = car
this.cdr = cdr
}
Envs.prototype.get = function (k) {
var e = this
while (e) {
if (e.car.get(k)) return e.car.get(k)
e = e.cdr
}
// returns undefined if lookup fails
}
Envs.prototype.set = function (k, v) {
var e = this
while (e) {
if (e.car.get(k)) {
e.car.set(k, v)
return this
}
e = e.cdr
}
throw new ReferenceError()
}
// Initialize the global scope.
var envs = new Envs(new Map(), null)
// We have to use this special function to call our closures.
function call(f, ...args) {
return f.func(f.envs, ...args)
}
// End of header.
var f = {
func: function (envs, x) {
envs = new Envs(new Map().set('x',x), envs)
envs.set('x', envs.get('x') + 10))
var g = {
func: function (envs) {
envs = new Envs(new Map(), envs)
return envs.set('x', envs.get('x') + 1).get('x')
},
envs: envs
}
return g
},
envs: envs
}
var h = call(f, 3)
console.log(call(h)) // 14
console.log(call(h)) // 15
var f = {
func: function f(envs, x, y) {
envs = new Envs(new Map().set('x',x).set('z',7), envs)
...
}
envs: envs
}
这是棘手的部分。转换的其余部分只是使用call
调用函数,并用envs中的查找替换对移动到堆中的变量的访问
有几个警告
x
和z
移动到堆中,而不是y
?答:最简单的(但可能不是最佳的)方法是将任何东西移动到封装函数体中引用的堆中envs
属性时,就会发生这种情况
最后,user3856986写道,“b()中的所有变量都成为c()的局部变量,而不是其他变量。调用c()的函数无法访问它们。”这有点误导。给定对闭包c
的引用,唯一阻止调用b
访问闭包变量的是类型系统。您当然可以从程序集访问这些变量(否则,c
如何访问它们?)。另一方面,对于c
的真正局部变量,在指定了c
的某个特定调用之前,询问您是否可以访问它们是没有意义的(如果我们考虑一些特定的调用,通过时间控制回到调用方,存储在它们中的信息可能已经被破坏)。
堆栈:
作用域与堆栈框架相关(在计算机科学中称为
“激活记录”,但大多数开发人员熟悉C或
程序集更熟悉堆栈框架)。作用域是堆栈框架的一部分
类与对象的关系,我的意思是对象所在的位置
堆栈框架是类的实例,堆栈框架是范围的实例
让我们以一种虚构的语言为例
javascript,函数定义范围。让我们看一个例子
代码:
当我们阅读上面的代码时,我们说变量aa
在范围内
在函数a
中,变量bb
在函数b
的范围内。
注意,我们不把它称为私有变量
私有变量的对立面是公共变量,两者都指
属性绑定到对象。相反,我们称为aa
和bb
。局部变量的反面是全局变量
(不是公共变量)
现在,让我们看看调用a
时会发生什么:
a()
被调用时,创建一个新的堆栈帧。为本地
堆栈上的变量:
堆栈:
┌────────┐
│ var aa│ 我写了一篇关于这个主题的文章:图解说明
要理解这个主题,我们需要知道范围对象(或LexicalEnvironment
s)是如何分配、使用和删除的。这种理解是了解全局的关键,也是了解闭包如何在幕后工作的关键
我不打算在这里重新键入整个文章,但作为一个简短的例子,请考虑这个脚本:
"use strict";
var foo = 1;
var bar = 2;
function myFunc() {
//-- define local-to-function variables
var a = 1;
var b = 2;
var foo = 3;
}
//-- and then, call it:
myFunc();
在执行顶层