javascript - 到底该怎么去理解闭包?
阿神
阿神 2017-04-10 15:40:19
[JavaScript讨论组]

今天看到了一段关于闭包的代码:

代码片段A:

!function(){
    var num=1;
    var exp={};
    function add(num){
        return num++;
    } 
    exp.getAddNum=function(){
        return add(num);
    }
    window.a=exp;
}()

console.log(a.getAddNum()); // 1
console.log(a.getAddNum()); // 1

代码片段B:

!function(){
    var num=1;
    var exp={};
    function add(){
        return num++;
    }
    exp.getAddNum=function(){
        return add();
    }
    window.a=exp;
}()

console.log(a.getAddNum());  // 1
console.log(a.getAddNum());  // 2

谁能解释下这2段代码的区别吗?考验大家基本功的时候到啦~~~~

阿神
阿神

闭关修行中......

全部回复(14)
PHP中文网

第一个里面是你传递进去的,他会使用当前作用域接收到的这个形参的值,它并没有去改变外层num的值,因此你每次用它来传递,值都是1。
而第二个的'add'方法中并没有num变量,他会通过作用域链找到外层的num,那么你这样调用时每次都是操作的外层变量的值,而这个值在你return之后是会累加的。

关于上下文和作用域链你可以看看这篇文章
图解Javascript上下文与作用域

伊谢尔伦

与闭包密切相关的两个概念是作用域链和词法作用域。简单解释下:

作用域链:JavaScript不存在大括号级的作用域,但有函数作用域,在函数内声明的变量在函数外不可见,而在代码块内声明的变量在代码块外是可见的。同时,一个作用域可以访问其内部变量,也可以访问其父级作用域的变量,比如函数内可以访问全局变量。

词法作用域:在声明一个函数的时候就会创建该函数的作用域环境,当其被调用的时候,它可以访问其内部作用域的变量,以及其父级作用域的变量。即便变量是在其之后声明的,一样可以访问。因为记录的是作用域范围,而不是作用域内的具体变量名。

举个例子

var a,b;
var c = function(){
   a = function(){
      console.log(b);
   };
   var d = function(){
   };
};
c();
var b = 1;
a();//1
typeof d;//undefined

全局函数a的声明是在全局作用域,b的赋值虽然在其后,但仍然可以访问。而局部函数d的声明则是在函数c的局部作用域里面,因此在全局作用域不能访问。

这时候闭包的作用就出来了!

来看一个最简单的代码

var e = (function(){
   var f = 1;
   return function(){
      console.log(f);
   }
})();
e();//1
typeof f;//undefined

变量f虽然是在闭包的局部作用域里,但由于e引用到了返回的匿名函数,而这个匿名函数处在变量f所在的作用域,可以访问之,因此在全局作用域里仍然可以得到f的值。

所以,闭包能够有效地减少对全局变量的依赖,并且保护局部变量(不能直接访问局部变量),同时延续局部变量寿命(在上面例子中,变量e保留了对返回的匿名函数的引用,因而其作用域没有在自调函数运行结束后被回收,变量f也就延续了寿命)。

好哒,最后我们来看一看,闭包如何面向对象进行设计。

闭包可以模仿其他面向对象语言的private和public。来举个PHP的例子吧:

<?php
class myClass(){
   private $name;
   function __construct($name){
      $this->name = $name;
   }
   function setName($name){
      $this->name = $name;
   }
   function getName(){
      return $this->name;
   }
}
$myName = new myName('defaultName');
$myName->setName('HaoyCn');
echo $myName->getName();
?>

那如果是在JavaScript里面我们怎么写呢?

第一,可以直接创建对象

var myName = {
   name: 'defaultName',
   setName: function(name){
      this.name = name;
   },
   getName: function(){
      return this.name;
   }
};
myName.setName('HaoyCn');
console.log(myName.getName());

第二,使用构造器

var MyName = function(){
   this.name = 'defaultName';
};
MyName.prototype.setName = function(name){
   this.name = name;
};
MyName.prototype.getName = function(){
   return this.name;
};
var myName = new MyName('defaultName');
myName.setName('HaoyCn');
console.log(myName.getName());

以上是使用JavaScript来设计对象,但name作为对象的属性,在JavaScript里面是完全暴露的,我们并不想这样。那怎么办?

var MyName = function(defaultName){
   var name = defaultName;
   return {
      setName: function(newName){
         name = newName;
      },
      getName: function(){
         return name;
      }
   };
};
var myName = MyName('defaultName');
myName.setName('HaoyCn');
console.log(myName.getName());

这样,name变量就完成了对private的模拟,同时返回了具有Getter和Setter作用的对象,可以操作name变量。

大家讲道理

这么跟你讲,一个养猪场里有一个火腿肠加工厂,猪送进去进行加工最后运出火腿肠,这个加工过程不会对猪场的其他猪产生影响,这个加工厂就叫做 闭! 包!

怪我咯

片段A里add(num)里操作的是自己的参数……每次调用的时候都是add(1),当然返回也是1.
片段B里add()里操作的才是闭包里那个变量num

你把A里的函数改成

function add(param){
    return param++
}

就好理解了

这都是不合理的变量命名造成的混淆……不在于对闭包的理解

PHP中文网

红皮书上有一章讲函数的参数是值传递去看下会有帮助

天蓬老师

第一个例子中的add方法将num作为形参传递进方法,在该方法的内部实际是该变量的一个副本,因此自增时是不会改变外部num的值的,所以每次调用的结果都为1。 第二个例子中没有将num传递进add方法,那么每次调用add时会从上级的作用域中去寻找该变量。因此add方法内num自增时也就会对外部的num值修改了。

PHPz

第一个例子中的add方法有混淆视听的作用,add中的参数就是一个形参,还定义成num,迷惑人。

大家讲道理
function add(num){
    return num++;
} 
exp.getAddNum=function(){
    return add(num);
}

每次调用exp.getAddNum(),便调用了add(num)方法,此时传进add()方法的num是一个参数,所以在add中的num++操作只针对add()函数内部的num参数,外面的num是不受影响的

迷茫

我只想说,这个例子太啰嗦了,要是想对比,下面这样就足够了

var num=1;
function x(){
  return num++;
}

上面的代码对应例子A

var num = 1;
function x(num){
  return num++;
}

上面的代码对应例子B

运行代码还是

console.log(x());
console.log(x());
PHPz

在第一段代码中,首先var num = 1为值类型,在调用add(num)时中的实参num复制了var num = 1的值,然后将值传递给定义函数时的命名参数num,在函数调用运行时,这个命名参数num是作为此函数的局部变量运行。
在第二段代码中在调用add()时,此时的var num = 1是作为嵌套函数add外部作用域的变量被使用,形成了闭包,因此结果被累加。
可以看看我的这篇文章,执行环境与作用域链以及函数执行

这是理解闭包的前提

关于函数参数传递可以看看这篇文章js中值的访问与参数传递的问题

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

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