简单小结一下关于JavaScript中的闭包

来源:互联网 发布:开源财务系统 php 编辑:程序博客网 时间:2024/06/05 13:29

1. 概念理解

  闭包是js中比较难懂的概念,在我看来,这除了他本身确实有难度外,还有一个很大的原因是“闭包”这个翻译,不太亲和,至少对于我来说是如此。

  定义:闭包是一个函数在创建时允许自身函数访问并操作自身函数之外的变量时所创建的作用域;即可以让函数访问所有的变量和函数,只要这些变量和函数存在于该函数声明的作用域内

  作为一个新手,看到这个定义的时候可是一头雾水,查阅了许多资料后,终于能有自己的理解了:一般来讲,在函数中的变量(包括函数),在这个函数执行之后会消失,你没办法从外部访问这些变量(相当于讲函数内部是封闭的),但是通过闭包,可以实现对这些变量和函数的访问。我自己写了一个含有闭包两个字的概念,或许能帮助你更好的记忆:调用函数时,词法作用域能括这个函数定义时所处的封的词法作用域。

2. 例子

不结合实践的理念都是耍流氓,下面举例子。
你肯定写过很多次如下类型的代码

//代码1var a = "mao";function outer(){    console.log(a);};outer();

  在这个例子中,函数outer访问了自身之外的变量a,然后对它进行输出,你可以这样看,此时你创建了一个气泡,这个气泡把函数outer和变量包起来了(实际上这个气泡即全局作用域)。单单从这个例子来看,也许会觉得,这没什么作用吧?至于弄出一个专门的概念吗?这只是闭包的最简单的例子而已,看了下面的例子,你就能体会到闭包,确实是很重要的。

//代码2function foo(){    var a = 2;    function bar(){        console.log(a);    }};

  如果我想调用foo中的bar呢(访问foo中的变量a)?在全局中直接bar();是不行的。怎么办呢,要对foo小改造一下。

//代码3function foo(){    var a = 2;    function bar(){        console.log(a);    }    return bar;//new add};var baz = foo();baz();

  分解一下,foo()将返回bar,再将bar赋值给baz,然会执行baz,复习一下定义:此处,函数bar在被调用时能够访问自身被定义时所在的词法作用域(指function foo(){……})中的变量a,就产生了闭包。这里体现了闭包的作用:本来foo();已经执行了,执行过程中的变量本应该被清除,但是闭包阻止了这个行为。

  学习了闭包,可以解决一些常见的错误。比如说,让控制台每隔一秒输出总共过了几秒(一开始输出0,1秒后输出1,2秒后输出2),你可能会这么写

for(var i = 0; i < 5; i++){    setTimeout(function(){        console.log(i);    },i*1000)}

  很遗憾,上面的代码,每隔一秒都是输出5,5是终止循环的条件,每次调用匿名函数时,丢失了当初定义该函数时的i,要满足要求,则要setTimeout中的匿名函数能够在被调用时访问到自身被定义时的i的值,
对比一下上面的例子,应该让匿名函数返回一个函数

for(var i = 0; i < 5; i++){    setTimeout(function foo(){        //console.log(i);        var a = i;        function bar(){            console.log(a);        }        return bar    }(),i*1000)};

  分析一下上面的代码,我把匿名函数加了个名字foo,方便讲解(这种做法其实也有其他好处),首先你得看到,foo(){…..}后面还有一个括号,代表此处不仅定义了foo,还执行了,实际上作为setTimeout的参数的是bar,而这个bar能记住每次循环中的i的值(可能不是记住,而是刻在了自己身上:在时间链上一共注册了5个事件,console.log(0),console.log(1),console.log(2)……),时间一到,就能正确输出了。

更高级一点的写法:

for(var i = 0; i < 5; i++){    setTimeout(function (a){        return function(){            console.log(a);        }    }(i),i*1000)};

  再说一个真实的应用,在一个项目中,需要根据用户角色来显示不同的功能按钮,我需要根据后台传过来的功能数组动态生成按钮并绑定它们点击事件:跳转到对应页面
没学闭包的时候,我是这么写

for (var i = 0; i < option_array.length; i++) {    var btn = document.createElement("button");    btn.className = "btn btn-default";    btn.innerText = "进入";    btn.onclick = function() {        window.location = "/Option/" + option_array[i] + "?user_id=" + user_id;    };}

错了,这样子每个按钮都会跳转到同个页面(一开始我还没发现,因为这个阶段每个页面都没什么内容,不是很容易注意到是同一个),后来发现错误了,当时还不知道闭包的概念,自己弄了方法,把对应的值存储在btn的value中,然后通过this.value来取得

for (var i = 0; i < option_array.length; i++) {    var btn = document.createElement("button");    btn.className = "btn btn-default";    btn.value = option_array[i];    btn.innerText = "进入";    btn.onclick = function() {        window.location = "/Option/" + this.value + "?user_id=" + user_id;    };}

其实这是走了远路,学习了闭包后,又做了让我满意的修改

for (var i = 0; i < option_array.length; i++) {    var btn = document.createElement("button");    btn.className = "btn btn-default";    //btn.value = option_array[i];    btn.innerText = "进入";    btn.onclick = (function(j) {        return function() {            window.location = "/Option/" + option_array[j] + "?user_id=" + user_id;        }    })(i);} 

改了之后,是不是变得高大上呢?
2017/5/14 更新
对于一开始setTimeout的例子,利用es6的let,能更简短地解决问题

for(let i = 0; i < 5; i++){    setTimeout(function(){        console.log(i);    },i*1000)}

完毕!如有错误,欢迎讨论!

参考资料
http://www.qdfuns.com/notes/17398/e8a1ce8f863e8b5abb530069b388a158.html
《你不知道的JavaScript》

0 0
原创粉丝点击