function foo(x) {
var tmp = 3;
return function (y) {
alert(x + y + (++tmp));
}
}
var bar = foo(2);
bar(10);
为什么上面这段代码每次调用bar时它都会自加1.但是下面这段代码每次执行又永远不会变化,恒为16
function foo(x) {
var tmp = 3;
function bar(y) {
alert(x + y + (++tmp));
}
bar(10);
}
foo(2)
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号
第二个例子里面并没有闭包,因为你没有返回一个函数,所以每次调用foo(x)都会重新定义新的tmp,但是第一个因为有闭包的存在,tmp在foo()执行之后仍然是存在的,因为tmp这个变量是可以被返回的bar()访问的,这个tmp并没有在foo()执行后就销毁,所以每次累加都会导致tmp变大。
因为在第二个里面
bar不是同一个bar。(每周都有这种问题,看样子你也初步理解了闭包,所以这次讲的稍微深一点儿)
在 JS 中,每一句代码都是在某个执行环境中执行的,各个变量就存储在执行环境中。执行环境是按照树的结构组成的,其根是全局环境,除了全局环境每一个执行环境都有一个父执行环境。变量的查找规则就是现在当前执行环境中查找,如果没找到就在父执行环境中查找,再在父执行环境的父执行环境中查找。。。一直查找到全局。
在JS中(ES2016之前,不谈eval)只有每次函数调用的时候才会创建一个新的执行环境。函数中的代码都是在这个执行环境中执行的。那么创建这个执行环境的时候其父执行环境是如何确定的呢?
其实在函数定义的时候会保存当前执行环境,即在解释器内函数其实是类似于这样一个对象:
需要说明的是,在函数未执行之前(不考虑解释器的优化)函数体是以字符串的形式存在的,编译器也不会去解析这段代码,所以即使有语法等错误只要这个函数不执行就不会有错误提示。
对于第一段代码,在执行任何语句之前有一个全局环境
global。然后我们来看第一句,他是在全局执行环境中执行的,这句话创建了一个函数。所以编译器在global环境中创建了一个叫做foo的变量,其中保存的内容就是上面给出的哪个对象,其中closure属性就是当前执行环境global,即global.foo.closure=global。下面一句是调用
foo(在全局环境中),所以在全局环境中查找这个函数对象。于是就找到了上面的对象,然后调用该函数。前面说过,函数调用是要创建执行环境的,设这个执行环境叫做foo_1,它的父执行环境就是global.foo.closure也就是全局执行环境(然后在该执行环境中定义形参,确定形参的值),然后执行global.foo.body中的代码。其中第一句定义了一个变量tmp,所以就在foo_1创建这个变量,即foo_1.tmp = 3。还定义了一个函数(没有名字,为了方便称作bar),由于这个函数是在foo_1中创建的,所以说bar.closure = foo_1;。当后面调用bar的时候,又会创建一个执行环境
bar_1,父级执行环境是foo_1(bar.closure == foo_1;),于是形成了bar_1 -> foo_1 -> global这样一个树的分支。根据前面说的变量查找规则,所以第一次执行bar的时候,其中访问的tmp是在foo_1中定义的哪个。然后在一次调用bar,又会创建一个执行环境
bar_2,由于bar.closure == foo_1;所以,其父级执行环境还是foo_1,所以形成了bar_2 -> foo_1 -> global这一个分支,所以访问的tmp和第一调用的时候是同一个变量。。。后面调用 bar 也是一样的。但是第二段代码就不一样了。
这样写是要多次调用函数
foo。调用的时候创建一个执行环境foo_1其中定义变量tmp,bar = { closure:foo_1 ,/*其他略*/}后面调用 bar 也是在foo_1中,所以这个 bar 是foo_1.bar。生成的执行环境是bar_1 -> foo_1 -> global,所以其中访问的tmp在foo_1中。当第二次调用的时候,创建一个执行环境
foo_2其中定义变量tmp,bar = { closure:foo_2 ,/*其他略*/}后面调用 bar 也是在foo_2中,所以这个 bar 是foo_2.bar。调用时生成的执行环境是bar_2 -> foo_2 -> global,所以其中访问的tmp在foo_2中,跟第一次调用显然不是同一个。参考我的另一个回答
https://segmentfault.com/q/10...
首先闭包返回的函数可以引用外层函数的参数和局部变量,第一个例子返回的函数中保存了tmp变量,每次调用bar()时都会使用上次tmp变量的值,第二个例子中不是闭包,闭包是一种程序结构:将一个函数作为返回值返回,而变量、参数保存在返回的函数中。。
因为第二种的foo函数作用域在它的作用域之外不存在对它的引用,也就是不存在闭包,所以第二种每次foo(2)执行完后都会被垃圾清理掉(垃圾回收器会清理掉零引用的对象),每次执行都是新的开始!
因为第二种情况每次执行foo 的时候tmp都会被初始化一次