再次理解闭包

什么是闭包

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

参考

闭包热身

普通循环

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;

推荐阅读更多精彩内容

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