最近在研究 JavaScript 中闭包,发现 A 函数返回值是其内部定义的 B 函数对象时,就算 A 函数执行完,因为词法环境的缘故,A 函数内部的局部变量也不会被销毁。
类似的 python 中闭包也有这个特性
而这个和我理解 C 语言或汇编的调用栈,一个函数执行完,调用栈中所有的变量都会被销毁,完全不一样
直到我看到别人博客中一句话:
“ 汇编或 C 语言中的栈和高级语言中栈是完全不同的。JavaScript 中闭包是在堆中申请的,使用 gc 管理,而汇编栈不存在 gc ,由函数调用与返回来自动更新 SP 指针实现的。所以在高级语言的闭包中其父级作用域在函数结束后不会被销毁 ”
他这句话对吗?高级语言的函数调用栈是都在堆中申请的吗?
function User(age){
this.age = age
this.changeAge = function(){
this.age += 1
console.log(this.age)
}
}
let tom = new User(18)
tom.changeAge() // 19
tom.changeAge() // 20 ,而不是 19
所以 js 的构造函数可以反复使用方法修改属性就是借助闭包实现的是吗?
class User:
def __init__(self, age):
self.age = age
def changeAge(self):
self.age += 1
print(self.age)
tom = User(18)
tom.changeAge() // 19
tom.changeAge() // 20 ,而不是 19
类似 python 的类的实现细节中,对象的方法可以反复修改对象的属性也是借助闭包实现是吗?
1
SmiteChow 2022-08-30 17:45:15 +08:00
闭包在堆上,不在栈上
|
2
SmiteChow 2022-08-30 17:46:38 +08:00
函数调用栈当然是在栈上,并不冲突
|
3
SmiteChow 2022-08-30 17:48:27 +08:00
你举的例子都是对象上的 member 操作,跟闭包沾不上边
|
4
yezheyu OP @SmiteChow 像 js 的构造函数不就是用 function 关键字声明的,不就可以看成一种特殊函数吗?使用其方法,不就是使用其内部的函数,内部函数用到其外层的属性,这结构不就和闭包结构很相似吗
|
5
yezheyu OP |
6
secondwtq 2022-08-30 19:26:29 +08:00
什么堆啊栈啊的,C 标准里面压根没这俩词儿,JS 标准里面也没汇编里的栈。
|
7
DOLLOR 2022-08-30 19:40:16 +08:00
你举的这两个例子,都跟“闭包”没有关系呀。
闭包至少要有几个嵌套的作用域,或者说嵌套的函数。 |
8
momocraft 2022-08-30 19:46:52 +08:00
看得人想当堆栈警察 又没时间
|
9
Jooooooooo 2022-08-30 19:59:18 +08:00
这...完全是理解跑偏了吧. 你这问题和栈啊闭包啊一毛钱关系没有.
(虽然我不懂 js) 第二个值会变是因为操作的是同一个对象啊 |
10
zunceng 2022-08-30 20:05:07 +08:00
当年我还写蛋疼的 c++的时候 也玩闭包
变量定义成智能指针 lambda 上对这个 shared_ptr 值拷贝 不过和高级语言的 gc 性质不太一样而已 |
11
Track13 2022-08-30 20:12:34 +08:00 via Android
function foo() {
let a = 1 return function() { console.log(a) } } const c = foo() c() 这才是闭包 |
12
lisongeee 2022-08-30 20:13:03 +08:00
> 所以在高级语言的闭包中其父级作用域在函数结束后不会被销毁
对于 v8 这个 js 引擎,如果子作用域使用了父作用域的变量,引擎会做静态代码分析,销毁父作用域后,把用到的变量包起来,打包给子函数,放到一个属性上 ![image]( https://user-images.githubusercontent.com/38517192/187432264-4ffd2936-d763-4408-8443-55a2609a8016.png) |
13
ScepterZ 2022-08-30 20:13:08 +08:00
你说的这个事和给的代码好像没一点关系
以 go 为例,编译器会做逃逸分析,如果变量有闭包之类后边还要用的情况,就照你说的,放到堆上,后边由 gc 回收,如果只是这里用,return 之后可以直接回收的话,就不会了 |
14
hangbale 2022-08-30 20:54:44 +08:00 via iPhone
这博客说的太模糊了,首先要先搞清楚运行时里的栈,堆是什么
另外,闭包=被闭包引用的值+ 代码,实现闭包关键要解决生命周期的问题 |
15
Al0rid4l 2022-08-30 20:59:00 +08:00
并不是函数嵌套就构成闭包, 而是嵌套的函数引用了外部的变量才构成闭包
|
16
yezheyu OP @Al0rid4l 我看很多博客都有说所有函数都是闭包,假如把 js 脚本文件看成 main 函数,脚本内部定义的函数不就相当于内部函数,函数名不就至少是个外部变量的引用吗?
https://zh.javascript.info/closure 就像这篇博客结尾说的 v8 引擎优化 |
17
yezheyu OP 函数名这个例子好像举的不太对,不过就算没有对外层函数的引用,这会因为词法环境的缘故普通函数也会形成闭包吧
|
18
yezheyu OP @hangbale
zh.javascript.info/closure 参考这篇博客,里面全篇没有提堆栈方面东西,所以像 js ,Python 这种跑在 Runtime 之上的软件,其调用栈规则其实和 C 的调用栈是没有参考性的是吗 |
19
yezheyu OP |
20
hangbale 2022-08-30 23:05:31 +08:00
@yezheyu 语言本身是没有堆栈一说,堆栈这两个东西的意义跟编译器如何分配程序中各种变量的内存空间以及管理它们的生命周期有关,所有的语言, 调用栈在运行形式上都是一样的,函数 outer 执行时, 它的活动记录入栈, 函数退出时 ,它的活动记录出栈,函数的局部数据也随之销毁,但是如果这个函数返回了一个函数 inner ,并且 inner 引用了 outer 的局部数据(闭包),那么 outer 的这些被引用的数据就不能被销毁,所以这些数据通常会移到堆上去,但是堆上的数据一般需要手动销毁或者写一个 GC 来管理。
js 与 c 不同的是,js 通常依赖 C++实现,所以在 js 里谈堆栈谈的是 C++的东西,另外 js 是动态类型,一切可变,这会很大程度上影响存储分配策略 |
22
Orlion 2022-08-31 19:05:38 +08:00
闭包其实挺好理解的,比如你有一个函数,会返回一个回调函数,这个回调函数中会持有一些自己的局部变量
function bar() { return function() { int tmp = 1; print(tmp); } } callback = bar(); callback(); 你当然也可以不返回回调函数,而是返回一个 class,就像下面这样: class C { private int tmp; public C(int tmp) { this.tmp = tmp; } public callback() { print(tmp); } } function foo() C { c = new C(1); return C; } foo().callback(); 达成的效果其实是一样的,返回的闭包函数其实就等价于一个带有私有属性和一个 method 的 class 。 |