到底该怎么去理解闭包?
今天看到了一段关于闭包的代码:
代码片段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段代码的区别吗?考验大家基本功的时候到啦~~~~
Answers
刚好我有整理过闭包这一块,可以看看, http://www.cnblogs.com/skylor/p/4721816.html
在第一段代码中,首先
var num = 1
为值类型,在调用add(num)时中的实参num复制了
var num = 1
的值,然后将值传递给定义函数时的命名参数num,在函数调用运行时,这个命名参数num是作为此函数的局部变量运行。
在第二段代码中在调用
add()
时,此时的
var num = 1
是作为嵌套函数add外部作用域的变量被使用,形成了闭包,因此结果被累加。
可以看看我的这篇文章,
执行环境与作用域链以及函数执行
这是理解闭包的前提
关于函数参数传递可以看看这篇文章 js中值的访问与参数传递的问题
与闭包密切相关的两个概念是作用域链和词法作用域。简单解释下:
作用域链: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
变量。