更多
更多
文章目录
  1. 什么是闭包?
  2. 闭包的使用规则和一些副作用

Javascript之闭包(翻译)

原文地址:http://javascriptissexy.com/understand-javascript-closures-with-ease/#

使用闭包可以让我们写出更具有创造性,能简明清晰表达的代码。事实上我们都在经常使用闭包,不管你使用javascript的经验有多少,你都会不自觉的一次又一次用到它。当然有时候闭包可能会显得有些复杂超出了你的理解范围,但是读了这篇文章之后,你一定会更轻松容易的理解它,另外也会更吸引你在每天的js变成任务中使用它。

这是一篇相当短小的文章详细的介绍了javascrip的闭包,阅读之前你需要弄清楚javascript作用域,这可以帮助你更好的理解。

什么是闭包?

一个闭包是一个可以访问外部函数变量作用域的内部函数。

闭包可以有三个作用域链:定义在他自身函数内部的变量,他也可以使用父级函数的变量,还可以使用全局变量。

内部的函数不仅能使用外部函数的变量,还可以使用外部函数的参数,但是不能使用外部函数的arguments对象。

你可以很容易创建一个闭包,通过把一个函数添加到另一个函数内部就可以了。

javascript中的一个常见例子

1
2
3
4
5
6
7
8
9
10
function showName (firstName, lastName) {
var nameIntro = "Your name is ";
//这个内部函数可以使用外部函数的变量包括他的参数
function makeFullName () {
return nameIntro + firstName + " " + lastName;
}
return makeFullName ();
}

showName ("Michael", "Jackson"); // Your name is Michael Jackson

在Node.js中经常使用到闭包,在jquery或者很多javascript片段中也经常使用到闭包的概念。

jQuer中一个常见的例子

1
2
3
4
5
6
$(function() {
var selections = [];
$(".niners").click(function() { // 这个闭包可以访问selections的变量
selections.push (this.prop("name")); //更新selections的变量取值范围
});
});

闭包的使用规则和一些副作用

1.闭包可以使用外部函数的变量哪怕外部函数有返回值

关于闭包最重要的一个功能就是内部函数可以使用外部函数的变量,哪怕外部函数有了返回值。是的,这句话你没听错,当函数在javascript中执行时,他们使用相同的作用域链当他们一旦被创建,这意味着即便外层函数有了新的返回值,内部函数依然可以访问了外部函数的变量,看下面这个例子演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
function celebrityName (firstName) {
var nameIntro = "This celebrity is ";
// 这个内部函数可以访问外部函数的变量包括参数
function lastName (theLastName) {
return nameIntro + firstName + " " + theLastName;
}
return lastName;
}
var mjName = celebrityName ("Michael");
// 在这里外层函数celebrityName有一个返回值
//闭包在当外层函数有了返回值之后进行执行
// 这个闭包函数依然可以访问外层函数的变量和参数
mjName ("Jackson"); // This celebrity is Michael Jackson

2.闭包的储存值依据于外层函数的变量

他们并不会储存实际被返回的值,在闭包被执行之前它对外层函数的变量改变更感兴趣。这个特点让闭包在某些代码的处理方式上更具有积极性,比如一下这个私有变量的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function celebrityID () {
var celebrityID = 999;
//我们可以返回一个内部函数对象
//所有这些内部函数都可以访问外部函数的变量
return {
getID: function () {
//这个内部函数将返回一个新的celebrityId变量
//他将返回当前celebrityID的值,即便改变了他的id值
return celebrityID;
},
setID: function (theNewID) {
//这个内部函数将随时改变外层函数的变量值
celebrityID = theNewID;
}
}

}

var mjID = celebrityID ();
//这里,celebrityId的外层函数已经返回了一个新值
mjID.getID(); // 999
mjID.setID(567); // 改变外层函数的变量
mjID.getID();
//567他返回了一个新的celebrityId的变量值

3.闭包所引发的问题

因为闭包可以改变外层函数的变量,所以他们同样可以引起很多bug当外层函数执行for循环的时候,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//这个例子进行了详细的说明
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function () {
return uniqueID + i;
}
}

return theCelebrities;
}

var actionCelebs = [
{name:"Stallone", id:0},
{name:"Cruise", id:0},
{name:"Willis", id:0}];

var createIdForActionCelebs = celebrityIDCreator (actionCelebs);

var stalloneID = createIdForActionCelebs[0];
console.log(stalloneID.id()); // 103

前面这个函数,当匿名函数被执行时,i的值是3(即数组的长度),这个数字3被加到了uniqueID中即103,所以任何位置都可以得到数组的id=103,而不是类似的100,101,102。

发生这个情况的原因是,当我们在讨论前面这个案例的时候,这个闭包(即例子中的匿名函数)已经获取了外层函数的变量作为参考,而不是他的返回值,所以就像前面案例显示的那样我们可以利用闭包使用最新的变量值,这个例子中同样可以使用改变之后的i变量,因为外层函数执行了整个循环返回了最后的i值103.

为了弥补这个闭包所引发的负影响,你可以用立即执行函数(IIFE)像下面这样去使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function (j) { // 这个形参j是i传入这个立即执行函数中的
return function () {
return uniqueID + j; //每次迭代都将当前的i的值传入立即执行函数中并保存这个数组的当前值
} () //在函数的末尾添加一对括号(),我们可以立即执行他然后返回uniqueID+j的值,而不是返回整个函数
} (i); // 立即执行函数将i作为一个参数传进来
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];

var createIdForActionCelebs = celebrityIDCreator (actionCelebs);

var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id); // 100

var cruiseID = createIdForActionCelebs [1];
console.log(cruiseID.id); // 101
感谢阅读
公众号