作者:谢然
链接:https://www.zhihu.com/question/34547104/answer/198016466
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
所有的函数在运行时都会在其所在空间创建一个新的子平行空间,所有的参数,局部变量及在函数内创建的函数(不管是函数声明还是赋值给变量的函数表达式),都是在这个子平行空间内创建(出生)的,只有出生在这个平行空间内的代码可以访问到这个平行空间内的变量及函数。即使在这个空间内创建的函数被返回,并赋值给全局或者父平行空间的变量,它也还是出生在那个平行空间,它对变量的访问也还是从那个空间开始往更大的平行空间查找变量,直到全局变量。
由于平行空间内的函数在运行时又会在其所在空间创建更小的平行空间。所以如果在平行空间内创建的函数还有可能运行,则函数所在的平行空间及所有父空间都不会被销毁。
下面来画个图:
考虑如下JS代码:
var a=1
function f(b){
return functiong(c){
return functionh(d){
return a+b+c+d
}
}
}
var add2=f(2)
var add4=add2(2)
var add5=add2(3)
var r1=add4(10)
var r2=add5(10)
console.log(r1,r2)
先看上面的代码不运行,想想输出多少。
然后看下图(点击可以看大图):我解释一下,左边是代码,最大的黑框是全局空间,然后第11行的 f 运行时创建了较小的黑色框,并传入了2即形参b的值,用红色圈出来了,同时 f 的运行返回了函数g,被全局空间内的变量add2指向,add2再次调用两次,于是在较小的黑框空间内又创建出了两个空间,即两个蓝色的空间,传入的c分别为2和3,也在空间内画出并用红色圈出来了,同时函数g两次调用都返回了函数h,分别赋给了变量add4和add5,但注意add4和add5分别指向了从不同空间(即g两次运行时创建的两个空间),这两个函数是不同的,而且也处于不同的空间。
好,现在,看着图告诉我,r1和r2的值分别为多少?
最后说一下为什么要用平行空间来解释闭包,原因有三:
一、电影中一般的平行空间都是事物相同,但发展轨迹不同;对应于函数的运行,则是函数每次运行时源代码都相同,但是传入的参数不同,得到的结果也就不相同了。(电影《源代码》)
二、闭包可以深层次嵌套,空间也可以。(电影《异次元黑客》)
三、内层空间可以看到外层空间,但外层空间看不到内层空间,这个就更容易想明白了,房子也好车也好,呆在空间里都很容易看到外面,但外面就不容易看到里面了。(电影《盗梦空间》)(可能记忆有误,我记得是上层梦境在播放音乐时只有下层梦境可以听到)
事实上 JS 里闭包内对变量的访问跟CSS 中的属性继承也非常像,考虑如下 HTML 结构:
两个 div 都会继承 section 的属性,span 继承第一个 div 的属性,即后代元素可以会继承祖先元素的属性,但祖先元素不会继承后代元素的属性。对应到平行空间,则是内层空间能够看到外层空间,但外层空间不能看到内层空间。对应到闭包,则是内层函数可以看到外层函数内的局部变量,但外层看不到内层函数里的局部变量。
另外,span 属性不会继承第二个 div 的属性,两个 div 的属性也不会相互继承。对应到平行空间,则是相互并列的平行空间不能看到对方的内部,就像坐在 A 车里的人可以看到路上,但看不到坐在 B 车里的人。对应到闭包,则是函数每次运行时创建的局部作用域都是不相关的。
究其原因为什么会这么像,是因为 HTML 也好,JS 语言也好,还是空间结构也好,都同为树状结构。而观察前面的图会发现,高阶函数运行过程中创建的嵌套作用域也是树状结构。
最后,这个平行空间如果没被销毁的话,就形成了闭包;而变量从内层空间向外层空间查找的过程则是作用域链(Scope Chain)。
网友评论