Rumah > Soal Jawab > teks badan
javascript
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000
在这段代码中,result()它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。为什么会这样呢?尤其是第二次,为何输出的不是999呢?
nAdd=function(){n+=1}又起到了什么作用呢?
ringa_lee2017-04-10 15:23:29
首先要说的是,闭包是functional language里面的核心概念。
当出现高阶嵌套函数的时候,编译器会做closure convention闭包变换
,核心就是变量不在分配在stack上,而是分配在heap上。这就是为什么f1已经返回,但是n还能被+1的原因。
楼主给出的这个程序,实际上就是一个高阶嵌套函数。
1. 因为在函数里面有定义的函数,这是嵌套。pascal也是允许嵌套函数。
2. 高阶的原因是,函数可以所谓参数传递和返回,像我们熟悉的C语言。
但是当高阶和嵌套同时出现,就会造成麻烦,所以pascal和C都只能支持其中的一个。
我来分析一下这个程序的执行流。
1. var result=f1(); 返回了一个函数f2, 因此result为f2。这个高阶函数特性,参考C语言函数指针。
2. result(); 调用f2,显然输出999.
3. nAdd(); 这里需要注意,这个nAdd实际上在定义的时候是一个lambda,是一个匿名函数,功能是n+=1。定义时将这个函数赋值给nAdd。所以在此时,实际上是调用了n+=1.为什么能找到n?因为n在堆里面。
4. result(); 调用f2,显然输出1000.
最后一点,n在堆上如何被销毁,这个工作是垃圾收集器负责。当n不在被任何闭包的env引用的时候,会被回收。
PHPz2017-04-10 15:23:29
1 并没有在f1调用后被自动清除。为什么会这样呢?
答:没有清除正因为闭包的机制导致的。延长变量的作用域和生命周期
首先f1 函数的作用域链。1 指向全局,2 指向自身。自身里面包括,n=999 f2, nAdd这个函数没有加var声明,是全局变量。但是里面的n引用的是f1内部的n=999
然后 f2函数的作用域链。1 指向全局 2 指向f1的作用域 3,指向自己作用域
nAdd这个作用域链 1 指向全局,2 持有n=999引用,(我觉得也指向f1作用域),3 指向自己作用域
执行 var result=f1(); 这个楼主应该清楚,就是result就是指向f2函数
调用 result();弹出n.就是先去自己作用域找。没有。然后就去上级作用域f1里找。找到了。n=999,弹出999
执行nAdd(),这时,去自己作用域里找。没有n,所以去上级作用域。f1里。发现n,执行n=n+1,n变1000
再次执行result();和上一次一样。就是先去自己作用域找。没有。然后就去上级作用域f1里找。找到了 这时n=1000了。所以弹出1000
推荐楼主看<<javascript高级程序设计第三版>>里面讲解作用域链,楼主就明白了。
我可能说的不是十分准确。
高洛峰2017-04-10 15:23:29
1)关于闭包,就是变量n的问题
2)关于函数声明和函数定义表达式
声明语句定义的表达式其作用域在其声明所在的函数范围内,所以f2是一个局部函数,要访问它必须通过其嵌套函数进行,而nAdd通过一个函数表达式声明的变量,前面没有var关键字所以是个全局变量,函数对象在f1执行完后不会被垃圾回收,不通过f1也可以访问到
3)因nAdd其在函数中定义的,所以具有闭包的特性,变量n就能访问到
怪我咯2017-04-10 15:23:29
我的理解,nAdd是全局的,而且又在f1的内部,因此可以访问f1内部的n。
f2在f1的内部,外部无法访问,但是return后result就执行f2的任务。
nAdd执行,n增大1,n既是nAdd访问f1中的n,result()即alert 1000.
高洛峰2017-04-10 15:23:29
因为 n
还在被引用啊,还是有用的东西,怎么能被清除呢?
假如 n
被当作垃圾回收了,你下次再调用 nAdd
或 result
的时候,你怎么知道 n
代表的是哪个值呢?
怪我咯2017-04-10 15:23:29
这是闭包最基础的问题。
https://github.com/getify/You-Dont-Know-JS
把这里面的closure看完你就能明白闭包了。
keywords:垃圾回收,作用域链。
PHPz2017-04-10 15:23:29
给 @kikong 的回答赞一个。本题有两个函数都构成了闭包,一个是nAdd
作为全局变量,另一个是f2
,不过访问f2
需要result = f1();result.f2()
这样访问。假设你去掉了f2
函数,这个n
变量依然是不会销毁的,这个你可以通过在nAdd中输出n
试试。
闭包其实就是函数内部有函数,且这个里面的函数使用了外部函数的变量。这个时候,变量的生命期就变长了,貌似这样就实现了C语言中的static的作用。要销毁这个变量,就要在使用完后result = null
,解除引用,然后JS的内存管理器闲了就会把其销毁了。当然,你这道题,貌似销毁不了,因为nAdd
是作为全局变量访问n
的。
以上回答是我个人理解的,可能不对。仅供参考!
ringa_lee2017-04-10 15:23:29
因为在当你运行nAdd();时,当前域并没有找到n这个变量,便向上寻找n变量,而f1()父函数里存在n=999,便吧999+1=1000,这里的n已经改变了,所以当你下次运行f2()时,就会弹出1000。你可以看看http://www.hubwiz.com/exchange/556c112bd75ed54c3bee9dbb
天蓬老师2017-04-10 15:23:29
函数的执行顺序是
function f1(){
var n=999;
function f2(){
alert(n);
}
nAdd=function(){n+=1}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
阿神2017-04-10 15:23:29
其实这里的考点是2个。
1、闭包。2、作用域。
nAdd=function(){n+=1}虽然是写在函数内部的,但是并没写var,所以是指向window的全局函数。但是定义在函数内部,所以可以调用n。