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/)

本文参考

  • George
  • MDN
  • 阮一峰的网络日志-学习闭包

你可能感兴趣的:(JS闭包从繁到简)