javascript - js闭包的一个小问题.......
阿神
阿神 2017-04-11 13:12:17
[JavaScript讨论组]
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)
阿神
阿神

闭关修行中......

全部回复(6)
PHPz

第二个例子里面并没有闭包,因为你没有返回一个函数,所以每次调用foo(x)都会重新定义新的tmp,但是第一个因为有闭包的存在,tmp在foo()执行之后仍然是存在的,因为tmp这个变量是可以被返回的bar()访问的,这个tmp并没有在foo()执行后就销毁,所以每次累加都会导致tmp变大。

迷茫

因为在第二个里面bar不是同一个 bar

(每周都有这种问题,看样子你也初步理解了闭包,所以这次讲的稍微深一点儿)

在 JS 中,每一句代码都是在某个执行环境中执行的,各个变量就存储在执行环境中。执行环境是按照树的结构组成的,其根是全局环境,除了全局环境每一个执行环境都有一个父执行环境。变量的查找规则就是现在当前执行环境中查找,如果没找到就在父执行环境中查找,再在父执行环境的父执行环境中查找。。。一直查找到全局。

在JS中(ES2016之前,不谈eval)只有每次函数调用的时候才会创建一个新的执行环境。函数中的代码都是在这个执行环境中执行的。那么创建这个执行环境的时候其父执行环境是如何确定的呢?

其实在函数定义的时候会保存当前执行环境,即在解释器内函数其实是类似于这样一个对象:

{
    body:"...", // 函数体,字符串
    args:["..",".."], // 形参列表
    closure:{},   // 保存一个执行环境的引用
    // 其他必要/可选的东西
}

需要说明的是,在函数未执行之前(不考虑解释器的优化)函数体是以字符串的形式存在的,编译器也不会去解析这段代码,所以即使有语法等错误只要这个函数不执行就不会有错误提示。

对于第一段代码,在执行任何语句之前有一个全局环境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,所以其中访问的 tmpfoo_1 中。

当第二次调用的时候,创建一个执行环境foo_2其中定义变量tmp,bar = { closure:foo_2 ,/*其他略*/} 后面调用 bar 也是在foo_2中,所以这个 bar 是foo_2.bar。调用时生成的执行环境是bar_2 -> foo_2 -> global,所以其中访问的 tmpfoo_2 中,跟第一次调用显然不是同一个。

阿神

参考我的另一个回答

https://segmentfault.com/q/10...

大家讲道理

首先闭包返回的函数可以引用外层函数的参数和局部变量,第一个例子返回的函数中保存了tmp变量,每次调用bar()时都会使用上次tmp变量的值,第二个例子中不是闭包,闭包是一种程序结构:将一个函数作为返回值返回,而变量、参数保存在返回的函数中。。

天蓬老师

因为第二种的foo函数作用域在它的作用域之外不存在对它的引用,也就是不存在闭包,所以第二种每次foo(2)执行完后都会被垃圾清理掉(垃圾回收器会清理掉零引用的对象),每次执行都是新的开始!

PHP中文网

因为第二种情况每次执行foo 的时候tmp都会被初始化一次

热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号