JS闭包从繁到简

闭包的定义

当一个函数即便在离开了它的词法作用域(Lexical Scope)的情况下,仍然存储并可以存取它的词法作用域(Lexical Scope),这个函数就构成了闭包。由定义我们不难发现闭包和作用域有很多联系

闭包的作用

  • 实现变量的“缓存”(使用不好容易出现内存泄漏)。
  • 单列模式
  • 代码模块化
  • 。。。。。

闭包的表现

  • 闭包是一个函数。
  • 嵌套在另一个函数里面,并且调用其变量。
  • 被调用参数和变量不会被垃圾回收机制回收
function outer() {
    var local_var = "I'm local"; //不会被立刻回收
    return function inner() { //闭包函数
        console.log("local: " + local_var);
    }; 
}

闭包的实例讲解(最全面)

  1. 变量跨域访问的问题
var global_var = "I'm global";
function A() {
   var local_var = "I'm local";
   console.log("global: " + global_var)
}
console.log("local: " + local_var); 

这就是上面所提到的js作用域问题了,函数内形成自己的作用块级作用域,可以访问全局变量。反之却不行,全局作用域内,却不能访问块级作用域的变量。但如果我们想访问到呢,第一种方式当然就是设置为全局变量,第二种就是用闭包了,下面看第二种解决方案。

function outer() {
    var local_var = "I'm local"; //不会被立刻回收
    return function inner() { //闭包函数
        return local_var;
    }; 
}
var getLocal_var = new outer(); // 当然你也可以直接new outer()();
var local_varValue = getLocal_var();
  1. 最常见setTimeout函数问题
for (var i = 1; i <= 5; i++) {
    setTimeout( function timer(){
        console.log( i );
    }, i*1000 );
}

这个例子相信大家再熟悉不过了,首先timer()形成闭包函数。直觉上都会认为输出为1,2,3,4,5。很明显这个结论是错误,实际结果为6,6,6,6,6。
首先是js执行为单线程,所以当循环执行完之后才会开始执行setTimeout函数。所以最后执行的时候i已经变成6了,可能有人会问,每一个闭包不应该都会有自己的一份拷贝吗?恭喜你,你的理解是对的,但这个方法却没有这样做,它都是调用了i这个变量,也就是说没有保存自己的那一个i。那么问题也就好解决了,我们给它传一个属于自己i。

for (var i=1; i<=5; i++) {
    (function(j){
        setTimeout( function timer(){
            console.log( j );
        }, j*1000 );
    })(i);
}
  1. 来看一下阮一峰老师的思考题
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
    var this_ = this; // 把第二个集合在一起了
      return function(){
        return this.name+'加'+this_.name;
      };
    }
  };
  alert(object.getNameFunc()());

相信大家已经知道答案 this. name = The Window ,this_.name = My Object;,首先我们需要解析的是object.getNameFunc()()到底是怎么运行的呢?

首先 object.getNameFunc = function(){
    var this_ = this;
      return function(){
        return this.name+'加'+this_.name;
      };
    }
其次 object.getNameFunc() = function(){
        return this.name+'加'+this_.name;
      };

而最后的object.getNameFunc()()相信大家已经知道它执行的是哪一个函数了。这边还会涉及到一个this指向的问题。永远指向调用它的函数,如果没有就指向window。所以this_ 指向了object 对象 。而 this 没有指向任何对象,所以指向了window,这也就是答案的由来。

  1. 比较复杂的面试题
function fun(n,o) { // 第一个fun
 console.log(o)
 return {
  fun:function(m){ // 第二个fun
   return fun(m,n); // 还是第一个 fun
   }
 };
}
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指向的是哪一个,最终执行的函数是什么
1: fun(0) 最后返回的是什么,作用是什么?

   a = {
       fun:function(m){ // 这是返回
         return fun(m,n); 
    }
   }

而作用就是将n置为了0,可能有人会问,执行完不应会被回收吗?当然不是这样,这个n = 0这个参数被下面的函数所引用,所以不会被垃圾回收机制销毁,这就是闭包的作用之一。
a.fun(1) = fun(0).fun(1);
a.fun(2) = fun(0).fun(2);
实际上就是在执行 fun(1,n) fun(2,n) fun(3,n) 当然是有返回值的,我们这边不讨论,又因为n被赋值为0,所以其实就是在执行 fun(1,0) fun(2,0) fun(3,0) 这样相信大家都知道打印为0 0 0了吧

  • 2:fun(0).fun(1)相信大家都知道为0,但是我们还需要关注一下这个结果的返回值是什么?
  fun(0).fun(1) 最后执行的为fun(1,0) {n = 0,o = 0} 所以返回值依然是 {
         fun:function(m){ // 这是返回
         return fun(m,n); 
    } 

所以fun(0).fun(1).fun(2) 就是在执行fun(2,1) 打印1 n = 2 ,o = 1,以此类推 fun(0).fun(1).fun(2).fun(3) 打印2

  • 3:和第二题一样,fun(0).fun(1) 依次打印 underfined 和0 c.fun(2) = fun(0).fun(1).fun(2)
    根据第二题的分析为1,c.fun(3) = fun(0).fun(1).fun(3),这边不在是2而是1,虽然第二次的n已经变成了2,但是因为你又重新执行了fun(0).fun(1) n重新被赋值为1了,所以最后执行的就是fun(3,1),打印1

闭包常见用法

  1. 单利模式
var Singleton = (function () {
    var instance;
    function createInstance() {
        return new Object("I am the instance");
    }
    return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();
function run() {
    var instance1 = Singleton.getInstance();
    var instance2 = Singleton.getInstance();
    console.log("Same instance? " + (instance1 === instance2));  
}
  1. 代码模块化(闭包模拟私有方法)
function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];
    function doSomething() {
        console.log( something );
    }

    function doAnother() {
        console.log( another.join( " ! " ) );
    }

    return {
        doSomething: doSomething,
        doAnother: doAnother
    };
}

var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
  1. 对象与方法关联
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

#test-h1 {
  font-size: 1.5em;
}

#test-h2 {
  font-size: 1.2em;
}
#test-h3 {
  font-size: 1.2em;
}
可能以前我们需要点击来改变body字体的大小的方法为
document.getElementById('size-12').onclick =            function(){
            document.body.style.fontSize ='12px';
};
我们常见的做法就是,响应事件然后执行的函数,但如果需要改变大小的样式有很多,比如12px一个按钮,14px一个按钮,16px一个按钮,如果以前的方式来写就很麻烦,而闭包可以为我们提供便利
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
是不是很简单呢?

ES6 解决闭包

  1. 块级作用域
  2. let 替代 var
了解详情请阅读《ECMAScript 6 入门-阮一峰老师》(http://es6.ruanyifeng.com/)

本文参考

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

推荐阅读更多精彩内容