JavaScript闭包

阮一峰的博客:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

创建匿名函数并立即执行

理论上讲,创建一个匿名函数并立刻执行可以这么写:

function (x) { return x * x;}(3); // 9

由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:

(function (x) { return x * x }) (3); //9

高阶函数

一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。如map, reduce,sort,filter。通常情况下,求和的函数是这样定义的

var arr = [1, 2, 3, 4, 5]; 
function sum(arr){
    return arr.reduce(function(x,y){
        return x+y;
    })
}
sum(arr);
console.log(sum(arr)); // 15

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。某些时候我们不想立刻求结果,而是后面根据需要再求怎么办?可以不返回求和的结果,而是返回求和的函数!

function sum(arr){
     return function(){
        return arr.reduce(function(x,y){
            return x+y;
        })
     }
}
sum(arr)(); //15
console.log(sum(arr)());

实际上,我们就是在原来立刻返回结果的语句的外层又套了一层匿名函数并作为返回值,这样sum返回的是这个匿名函数,当再次调用这个匿名函数的时候,才是返回刚才想立刻返回的结果。我们也可以把这个匿名函数赋给一个变量来实现

function sum(arr){
var result = function(){
return arr.reduce(function(x,y){
return x+y;
})
}
return result;
}
sum(arr)(); //15

## 闭包
在上面例子中,我们在函数`sum`中又定义了函数result,并且,内部函数result可以引用外部函数sum
的参数和局部变量,当sum返回函数result时,相关参数和变量都保存在返回的函数中,这中程序结构就称为**闭包(Closure)**。

function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 16
f2(); // 16
f3(); // 16

**返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。**

如果一定要引用循环变量怎么办?方法是**再套一层匿名函数并立即执行,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变**:

//方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,
//无论该循环变量后续如何更改,已绑定到函数参数的值不变:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function(){
return n*n
}
})(i));
}
return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

console.log(f1()) //1
console.log(f2()) //4
console.log(f3()) //9

这里外层套了一层匿名函数,立即执行则相当于又把这层函数给抵消了,那么push的仍然是像我们在原来那个函数中push的函数,只不过这时push的那些函数的参数是我们创建的匿名函数带来的参数了。
我们这个立即执行的匿名函数就好像在原来的for循环和push的function之间加入了一层作用域,这个作用域变成了function () { return i \\* i; }的爹,使得里面的function () { return i * i; }的变量要先去找爹,再去找爷爷,爹中有参数,就不去找爷爷了?。
再来个例子巩固一下:
实现函数 makeClosures,调用之后满足如下条件:
1、返回一个函数数组 result,长度与 arr 相同
2、运行 result 中第 i 个函数,即 `result[i]()`,结果与 `fn(arr[i])` 相同 

var arr = [1, 2, 3, 4, 5];
var square = function (x) { return x * x; };
function makeClosures(arr, fn) {
var result = [];
for(var i = 0; i < arr.length; i++){

}
return result;

}
var funcs = makeClosures(arr, square);
console.log(funcs1); // answer: 4

首先我们肯定是要在for循环中添加内容。尝试1:

for(var i = 0; i < arr.length; i++){
result.push(fn(arr[i]));
}

这肯定是不对的,因为不符合题目要求,人家要返回函数啊,你这样直接result中存储的都是值了,还怎么能调用呢?虽然值是变了的,那是因为fn(arr[i])立即执行了(fn已经是写好的函数square了)。

那好吧,那我们外面加一层函数,然后把这个值通过这个函数返回不就得了么。尝试2:

for(var i = 0; i < arr.length; i++){
var tmp = arr[i]
result.push(function(){
return fn(tmp);
});
);
}
var funcs = makeClosures(arr, square);
// console.log(funcs)
console.log(funcs0); //25
console.log(funcs1); //25
console.log(funcs2); //25

这里就犯了和上面sum的例子的同样的错误,因为当我需要调用的时候,我的i循环变量已经结束循环,此时i=5, 所以输出的都是25了。
疑问:不写`var tmp = arr[i]`直接写成`return fn(arr[i])`为什么不行????

解决方法同理,就是再套一层函数,绑定参数,然后立即执行。这样就把作用域给变了。尝试3:
for(var i = 0; i < arr.length; i++){
     result.push((function(e){
        return function(){
            return fn(e);
        }
     })(arr[i]))
}

var funcs = makeClosures(arr, square);
// console.log(funcs)
console.log(funcs0);
console.log(funcs1);
console.log(funcs2);

这样就搞定了。。。


## 关于作用域
Q1:全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。这与模块的区别???
## 关于参数的作用域?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容

  • 前言 总括 :这篇文章使用有效的javascript代码向程序员们解释了闭包,大牛和功能型程序员请自行忽略。 译者...
    KX九五阅读 278评论 0 1
  • 目录 1.执行环境与作用域链 2. 立即执行函数 3. 闭包知识点 3.1 什么是闭包 3.2 使用闭包的意义与注...
    犯迷糊的小羊阅读 636评论 0 11
  • 前言 这篇文章使用有效的javascript代码向程序员们解释了闭包,大牛和功能型程序员请自行忽略。 基础篇 闭包...
    kiaizi阅读 363评论 0 7
  • 前言 总括 :这篇文章使用有效的javascript代码向程序员们解释了闭包,大牛和功能型程序员请自行忽略。 译者...
    秦至阅读 744评论 0 19
  • 毕业,工作,结婚,生子,继续工作。一路走来,这个陌生的都市见证了我在这里的努力,彷徨,欢笑和泪水。从那个长时间漂在...
    张小棠的简书阅读 342评论 0 1