再次理解闭包

什么是闭包

  • 一种写法
  • 在函数定义处的环境中自带数据
  • 一种为局部定义函数封装信息的方式

参考

闭包热身

普通循环

for (var i = 0; i < 5; i++) {
  console.log(i);
} //输出0 1 2 3 4 

延时循环

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000 * i);
} // 因为1秒后循环已经结束输出5个5

让延时循环输出0到4 (使用闭包)

for (var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i); //一个立即执行函数,用到了闭包,输出0 1 2 3 4,每次循环都把i保存了下来
}

热身结束,这其实是来自知乎上面一道关于JS运行机制的文章的前半段,运行机制以后再说,有兴趣的话可以先看 运行机制

一,变量作用域

变量作用域有两种:全局变量和局部变量。JavaScript语言规定,在函数内部可以直接读取全局变量。

    var n=999;
    function f1(){
      alert(n);
    }
    f1(); // 999

另一方面,在函数外部自然无法读取函数内的局部变量。

  function f1(){
    var n=999;
  }
  alert(n); // error

注意,函数内部声明变量的时候,一定要使用var命令。如果不使用,实际上声明了一个全局变量。

    function f1(){
        n=999;
    }
    f1();
    alert(n); // 999

二,如何从外部获取局部变量

如何从外部获取局部变量,那就是在函数的内部,再定义一个函数。

function f1(){
    var n=999;
    function f2(){
        alert(n); // 999
    }
}

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。对上面例子变通下。

function f1(){
    var n=999;
    function f2(){
        alert(n); 
    }
    return f2;
}
var result=f1();
result(); // 999

上面代码中,把函数f2作为返回值,那在函数f1外部,就可以获取它内部变量了。

三,闭包

上面代码中,函数f2,就是闭包。闭包就是能够读取其他函数内部变量的函数。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包作用有两个:

  • 可以读取函数内部的变量
  • 这些变量始终保持在内存中

例一:

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
        alert(n);
    }
    return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000

上面代码中,执行函数f1 返回函数f2result = f2result实际上就是闭包f2函数,它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。(具体阅读《JavaScript-内存》一章)

另外,nAdd=function(){n+=1} nAdd是一个全局变量,nAdd的值是一个匿名函数,这个函数本身也是一个闭包。

四,使用闭包注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

例子:

 function assignHandler() {
    var el = document.getElementById('demo');
    el.onclick = function() {
        console.log(el.id);
    }
}
assignHandler();

以上代码创建了作为el元素事件处理程序的闭包,而这个闭包又创建了一个循环引用,只要匿名函数存在,el的引用数至少为1,因些它所占用的内存就永完不会被回收。

function assignHandler() {
    var el = document.getElementById('demo');
    var id = el.id;

    el.onclick = function() {
        console.log(id);
    }

    el = null;
}
assignHandler();

把变量el设置null能够解除DOM对象的引用,确保正常回收其占用内存。

五,模仿块级作用域

任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。

(function(){
    //块级作用域
})();

六,实际例子

例一:

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()());

上面代码中,执行object.getNameFunc() 返回 function(){return this.name}在执行一次,相当于执行这个返回函数,得到 this.name 这里的this代表window,所以是全局变量 name

例二:

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());

上面代码和例一的区别是,把this保存起来,that 代表 object,得到的是 'My Object'

例三:(出自很多人都会做错的闭包题)

function fun(n,o) {
    console.log(o);
    return {
        fun:function(m){
            return fun(m,n);
        }
    };
}
var a = fun(0);a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?

/*
    解题前,需要知道第一个函数fun(n,o),第二个函数 fun: function(m){return fun(m,n)},第三个函数 fun(m,n) 其中第一和第三函数相同
    匿名函数有 var fn1=function (){} ,具名函数 function fn1(){} 和 var fn1=function a(){};
*/

/* 第一个

1, var a = fun(0); ---->执行fun(0)后, 返回一个对象 a = {fun:function (m){return fun(m,n)}} ,此时 n = 0, o = undefined
2, a.fun(1); ----->  返回一个函数fun(m,n),此时 m = 1, n = 0 由于闭包前面的变量不会被删除,所以fun(1,0) 执行后 console.log(o) = 0;
3, a.fun(2), a.fun(3) 和上面一样的结果
*/

/*第二个:链式调用,后面执行的函数调用前面返回的数据

1, var b = fun(0) 和上面第一步一样, 返回 {fun:function (m){return fun(m,n)}}   和 n = 0, o = undefined
2, 执行到 fun(1) 时, 返回 fun(m,n) , 此时 m = 1, n = 0 , 执行 fun(1,0) 后 console.log(o) = 0 返回 {fun:function (m){return fun(m,n)}} ,此时 n = 1, o = 0
3, 执行到 fun(2) 时, 返回 fun(m,n) , 此时 m = 2, n = 1 , 执行 fun(2,1) 后 console.log(o) = 1 返回 {fun:function (m){return fun(m,n)}} ,此时 n = 2, o = 1
4, 执行到 fun(3) 时, 返回 fun(m,n) , 此时 m = 3, n = 2 , 执行 fun(3,2) 后 console.log(o) = 2 返回 {fun:function (m){return fun(m,n)}} ,此时 m = 3, o = 2

*/

/*第三个

1, var c = fun(0).fun(1); 执行和第二个前两步骤一致,o = undefined 和 0
2, 执行到 c.fun(2)时,c = {fun:function (m){return fun(m,n)}} ,此时 n = 1, o = 0, 返回 fun(2,1)后 m = 2, n = 1 ,o = 1;
c.fun(3) 执行出来一致 o = 1;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,114评论 0 13
  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量...
    zock阅读 1,074评论 2 6
  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量...
    zouCode阅读 1,270评论 0 13
  • 在超市里,随手拿起一本笔记本,信手一翻,指间滑落一枚压干了的树叶。放回去,白纸间衬一片黑褐色叶片,煞是醒目,又带了...
    许蚀阅读 510评论 0 1
  • 听了银洪跟我讲他家里的事,,我现在还是在想着,想不明白,一个人的人生怎么还可以悲惨在这样的境地。那是我从来没有想过...
    简简夏阅读 87评论 0 0