Javascript 的闭包特性是否来源于某种设计失误?或是某种设计领先?

2021-02-26 00:34:10 +08:00
 LeeReamond

如题,最近听前端同事讨论 js 的闭包有感,这个帖子想讨论一下语言的命名空间设计,并非单纯局限在闭包问题。标题里写闭包是因为感觉这样比较容易被大家理解。

以下正文

=============

我个人学习 js 的过程中没怎么在意过闭包这个特性,我感觉命名空间逐级继承是一个再自然不过的设计,其他语言中不都是如此设计的?但是旁听别人讨论后发现 js 的命名空间确实跟其他语言不太一样,从设计原因上,似乎 js 的作者希望设计出一种特性能保存函数执行过程中的栈状态,不过我个人感觉上闭包这种使用方法又没有其他语言中生成器之类的特性描述能力强。

从使用角度来说,js 中命名空间的一个特性是子空间可以修改父空间内容,比如

var data = 0
var func = function(){
    data = 1
}
func()
console.log(data)

在这段代码中很显然 data 属于“全局变量”而被单个函数的调用过程所修改了。以前在写 js 的时候没注意,感觉自然也没什么奇怪之处。但是仔细想想,平时写其他语言时并不是这么处理的。比如 C 语言中要达到类似效果通常是通过传入指针的方式

#include<stdio.h>

void main(){
    int data = 0;
    void func(int *p){
        *p = 1;
    }
    func(&data)
    printf()
}

类似这样,即函数显然可以访问同级或上级作用域中的变量,但通过一个函数直接修改全局变量的指向是不被允许的。

在 python 当中可能用一些变通的结构

data = [0]
def func(data):
	data[0] = 1

或者使用 global 将全局变量暴露在函数中

data = 0
def func(data):
	global
	data = 2

不过说实话这种描述方式感觉比较局限,虽然理论上使用起来与 js 相当,但我在实际程序中几乎没有使用过 global 或者 nonlocal 之类的特性。

java 当中又回到类似 js 的模式,变量值可以直接被修改了。

============

不禁让人打起一个大大的问号,即为什么这种设计并不统一,有的语言仅可以读取,有的语言可以直接修改。他们都是出于什么目的设计这种特性的?

5000 次点击
所在节点    问与答
54 条回复
liprais
2021-02-26 00:37:30 +08:00
21 天搞出来的,要啥自行车
LeeReamond
2021-02-26 00:39:58 +08:00
@liprais 我个人使用过程中两种模式都没给我带来过任何不爽。但我其实很想知道如果以后还要设计更新的语言,那种模式是更合理的
wzb0909
2021-02-26 00:48:36 +08:00
思而不学则怠啊。

多看多做,多思考实际问题。然后,你就不会问这样的问题了。

好的问题让人进步,坏的问题让人原地转圈。

是的,回答你的问题可以让你进步,但是这进步反而又助长了你糟糕的习惯。

我再说一遍来总结:你的问题毫无意义。不值得被思考,不值得被回答。
tushankaka
2021-02-26 00:52:18 +08:00
Lisp
mxT52CRuqR6o5
2021-02-26 00:56:41 +08:00
首先得有内存管理才可能有闭包
LeeReamond
2021-02-26 01:13:28 +08:00
@wzb0909 谢谢,block 了
msg7086
2021-02-26 01:20:30 +08:00
Lisp 。

话说首先你 C 语言的例子就错了,data 不是全局变量,C 语言里也没有 nested function 。
你例子里 func 是和 main 平级的,func 当然不能访问 main()里的局部变量了。
seki
2021-02-26 01:36:45 +08:00
都是 ES6+ 时代了,正经前端写新的 JS 代码也不会上来一个 var,全局变量漫天飞了。这和你说的 C 语言的「通常」是一个道理
agagega
2021-02-26 01:50:25 +08:00
你说的这个闭包可以访问外部对象的特性并不奇葩,只不过不允许它访问实现起来可能简单一些。C++的 lambda 里自己可以设定是否捕获和怎么捕获(值还是引用),不过因为没有 GC,生命周期需要自己维护。如果你熟悉 Java 这种语言的话,不妨把 JS 的闭包等价理解成一个带 call 方法的类的对象,其捕获的外部内容就是除了 call 方法外这个类其他的成员变量。JS 真正奇特的设计在 this 的作用域,不是词法作用域而是动态作用域,配合原型链造就了一系列坑(和黑魔法)。
Mithril
2021-02-26 02:07:09 +08:00
你要理解一个事,程序的本质是数据以及针对数据的操作。
最原始的计算机是不区分这二者的,通过一个寄存器指向某个内存,读出的二进制都当“操作”,这个操作所引用的地方可能会被读出来当作数据。
然后 C 那个时代的编程将对数据的操作抽象成了函数,这时候数据就有了作用域之分。
再然后抽象层次更高一些的语言,就会想办法考虑如何将数据和操作更好的结合在一起。所有面向对象的语言设计了“类”这个概念。本质上就是给“数据”加上“操作”。
另外一些语言,或者语言的特性会尝试着反过来做。给“操作”绑定某些“数据”。比如 JS 的闭包概念,和 C++等语言的 lambda 函数。
两种方法只是设计的重心不同而已。
himself65
2021-02-26 02:51:22 +08:00
nested function 是 gnu c 的独有语法

https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html

@msg7086
msg7086
2021-02-26 03:02:50 +08:00
@himself65 是啊,GNU C 里的。
不过,如果是 GNU C 里的 nested functions 的话,GNU C 是支持 nested functions 访问外层局部变量的。
msg7086
2021-02-26 03:07:14 +08:00
上面#10 说的其实很好。
面向对象设计是给数据绑上了方法。
面向闭包设计是给方法绑上了数据。
LeeReamond
2021-02-26 03:50:47 +08:00
@seki 这里只是举例,不讨论实现本身,用 var 和 let 不影响代码行为,我觉得用 var 更能体现一些特点所以用了 var
LeeReamond
2021-02-26 03:53:30 +08:00
@Mithril 感谢回复,但我觉得这个不算做问题的解答,因为单纯讨论特性的话大部分语言往往兼具两种,即他可以面向对象,同时也可以使用闭包保存状态,但是这不影响不同语言对命名空间的处理
LeeReamond
2021-02-26 03:55:37 +08:00
@msg7086 我是手机发帖没法直接测试,我上文的代码里 gcc 中 func 可以直接修改 data ?这么魔幻
msg7086
2021-02-26 04:09:38 +08:00
@LeeReamond

#include <stdio.h>

int main() {
int data = 0;
void func() {
data = 3;
}
printf("%d\n", data);
func();
printf("%d\n", data);
}

# ./test
0
3
LeeReamond
2021-02-26 04:15:00 +08:00
@msg7086 比较震惊,有种这么多年 C 语言学的都是错的感觉。C 是少数专门啃过语法书的语言,但是这么多年其实没怎么写过。所以意思是主流语言的设计,GNU 的 C,java,js,都是可以实现能访问即能修改的,py 属于异类?那么禁止修改这是一种错误的设计么
msg7086
2021-02-26 04:29:19 +08:00
@LeeReamond
Again,这不是 C 语言,这是 GNU C 扩展。nested functions 不是 C 标准。

然后,不实现一种功能并不意味着是禁止你。
比如说你的共享单车没法跑到 200 公里的速度,并不是因为单车禁止你骑那么快,只是因为你腿没那么大力而已。

比如说,C 语言,不支持面向对象编程,不支持面向闭包编程,不支持函数式编程。
(免杠声明:强行做是可以做的,只是没有原生的语言特性支持而已。)
所以就只是不支持而已,他没这能力。
你不能说他禁止你做这些事情。
他连做这些事情的能力都没有,怎么禁止?

至于 Python 修改全局变量的问题。
当然是可以修改的,方法你也写出来了,就是用 global 。
你不能说 Python 提供了 global,你不用,还强行说他不能用吧。

再还有,不要非黑即白。任何设计都有两面性。
比如 Python 和 Ruby 就是两个看上去很像,但是设计理念完完全全相反的语言。
但是你不能说其中一个就是错误的。
Python 更死板,所以写起来很难受(冗长),但是适合大团队量产程序员用,人人都能写出差不多的代码。
Ruby 更灵活,干一件事情能有十种写法(而且大都能写得很漂亮),但是一不小心黑魔法用多了,就只剩上帝能看懂了。

如果每种语言都是同样的设计,那还要那么多语言干什么,只留下其中一个就行了。
msg7086
2021-02-26 04:36:24 +08:00
另外,不同的语言在设计上差得很远很远。
比如同样是面向对象的 Java 和 Ruby,我可以举个简单的例子。
在 Java 里,如果有代码 obj1.data = 1,Java 会在 obj1 里找到 data 字段,然后把 1 赋值上去。
在 Ruby 里,同样的代码,Ruby 会给 obj1 发送一个消息,执行一个叫 data= 的函数,把 1 作为参数传过去。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/756350

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX