(这篇文章后面关于onclick事件的解释是错误的,请不要被误导了2016.6.16)

闭包这个概念给JavaScript初学者心中留下了巨大的阴影,网络上关于闭包的文章不可谓不多,但是能让初学者看懂的很少,所以这里我将用尽量浅显的语言来解释这个概念,以裨初学者参考。

闭包是什么?很简单,闭包就是可以访问其他函数作用域的中的变量的函数。那么什么函数可以访问其他函数中的私有变量呢?当然是在函数内部定义的函数可以访问父函数中的变量。所以理论上来讲,当我们在一个函数内部定义了一个函数的时候,这个子函数就可以叫做闭包了。例如:

function foo(){
var bar = 0;
function boo(){ //我是一个闭包
return ++bar;
}
console.log(f1());//
console.log(f1());//
console.log(f1());//
}

理论上来讲,这里的boo就是一个闭包。因为它可以访问其他函数作用域中的变量(bar),不过这样的闭包没有什么用,因为闭包没有出口,它会随着父函数的终结而终结,也就不会产生那些奇妙的效果。

我们来看下面的代码:

function foo(){
var bar = 0;
return function boo(){ //我是一个闭包
return ++bar;
}
}
var f1 = foo();
console.log(f1());//
console.log(f1());//
console.log(f1());//

这个例子中我们把子函数boo作为父函数的返回值输出到外面,这下它终于可以重见天日了。它可以在父函数死了以后继续存在,而不是被包在里面随着foo函数的终结而终结。我们给变量f1赋值为foo(),那么f1就是foo return回来的值,也就是一个函数。当赋值结束后,foo函数生命终止。而boo(被赋给f1)继续存在。这时候它仍然可以访问已经死去的父函数中的变量bar。这种形式就是我们最常见的闭包形式。

为什么boo还可以访问bar呢?因为当创建一个函数时,系统会自动为他创建一个作用域链,作用域链中保存着一系列的变量对象(变量对象:执行环境创建后,系统自动把执行环境中的所有变量打包成的对象),首先是由它自己的所有变量组成的变量对象,然后是它父函数的变量对象,一直到全局作用域。只要函数本身没有死亡,它的作用域链中存在的变量对象也永远不会被系统回收,即使那个变量对象对应的本身的函数已经死亡。这里变量bar是父函数中的变量,所以它会一直保存在子函数boo的作用域链里,因而boo就永远可以访问它。这也就是“闭包”这个名词的来源——即闭包执行时它的作用域链中打包了所有父环境中的变量,可以随时使用,不管父环境是否消亡。

当父函数死亡后,闭包脱离父环境单独在外面执行时,就会发生一些我们不想见到的状况(副作用)。看下面的代码:

function createArray(){
var result = new Array(); for(var i=0;i<10;i++){
result[i] = function(){
return i;
};
}
return result;
} var c1 = createArray();
console.log(c1[0]());//
console.log(c1[1]());//
console.log(c1[2]());//

我们期望每次得到的是0,1,2,3...,可实际上每次都得到10。我们来分析一下这段代码。在父函数内部,我们先定义了一个数组,然后通过for循环,把它赋值为一个函数(为什么不直接赋值为i呢,这可能是因为我们在赋值之前还想添加其他代码),这时候,result数组的每一个元素都是一个小闭包。当我们令c1 =createArray()时,我们得到十个小闭包。这时候,createArray已经死了。当这十个小闭包要引用i的时候,只能从它们的作用域链里找到一张照片(snapshot),也就是for最后一次执行后i的值也就是10,于是结果永远等于10。

怎么解决这个问题呢?请看下面的代码:

function createArray(){
var result = new Array(); for(var i=0;i<10;i++){
result[i] = (function(num){
return function(){
return num;
}
})(i);
}
return result;
} var c1 = createArray();
console.log(c1[0]());//0
console.log(c1[1]());//
console.log(c1[2]());//2

很多人遇到这样的代码就说这是在利用闭包来解决问题。实际上正好相反,这是在用立即执行的匿名函数来消除闭包的副作用。我们创建了一个立即执行的匿名函数,这个匿名函数是原来的数组赋值函数的父函数,这就相当于在赋值函数的作用域链里插入了新的变量对象,每次匿名函数执行的时候,立即把i的值给了num,因此小闭包的作用域链里就有了新的变量num,这样在外部执行的时候它就不会直接去找i了,而会先找到num,于是我们的问题就解决了。

再来一个栗子,当我们想给HTML中元素批量添加事件时,常常会发生只执行最后一个事件的状况,看下面的代码:

//给li元素批量添加click事件
window.onload = function(){
var lists = document.getElementsByTagName("li");
for(var i=0;i<lists.length;i++){
lists[i].onclick = function(){  //我是闭包
alert(i);
}
}
} //修改后的代码
window.onload = function(){
var lists = document.getElementsByTagName("li");
for(var i=0;i<lists.length;i++){
lists[i].onclick = (function(num){//我是匿名函数,我的作用是给闭包的作用域链中增加一个变量
return function(){
alert(num);
}
})(i);
}
}

这段代码中,onclick触发事件响应函数的时候,父函数(onload的响应函数)早已经死了,出现了我们熟悉的状况——子函数脱离父函数单独执行。这时子函数作用域链里面存i的只是最后一次for循环后的值,解决方法当然是创建匿名函数(作为事件响应函数的父函数)来马上接收每次循环i的值。

总结上面的内容发现,一个讽刺的事实是,很多时候你以为你在用闭包解决问题,实际上你只是在用匿名函数消除闭包的副作用而已。

那么,闭包的真正用武之地在哪里呢?答案是数据安全,实现私有变量。javascript中有句话叫,全局变量是魔鬼。为了防止命名冲突和恶意篡改,我们通常尽量不定义全局变量。看下面的代码:

var addSelf = (function(){
var count = 0; //我将会成为闭包里的私有变量
return function(){ //我是一个闭包
return count++;
}
})()

这段代码创建了一个自增器,父函数的作用域中定义了一个变量count,父函数是立即执行函数,执行完毕生命周期结束,返回一个自增器子函数,又一次,这个子函数脱离定义它的父函数环境执行,这个时候count仅仅存在于子函数的作用域链里,对于其他所有人都是不可见的,这就很好的保证了数据安全。

理解JavaScript中的闭包的更多相关文章

  1. 深入理解JavaScript中的闭包

    闭包没有想象的那么简单 闭包的概念在JavaScript中占据了十分重要的地位,有不少开发者分不清匿名函数和闭包的概念,把它们混为一谈,我希望借这篇文章能够让大家对闭包有一个清晰的认识. 大家都知道变 ...

  2. 【原】理解javascript中的闭包

    闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 什么是闭包? 官方说法: 闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见 ...

  3. 深入理解javascript中的闭包!(转)

    1.闭包的经典错误 假如页面上有若干个div,我们想给它每个绑定一个onclick方法,于是有了下面的代码. function A(){ var divs=document.getElementsBy ...

  4. 【原】理解javascript中的闭包(***********************************************)

    阅读目录 什么是闭包? 闭包的特性 闭包的作用: 闭包的代码示例 注意事项 总结 闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 回到顶 ...

  5. 全面理解JavaScript中的闭包的含义及用法

    1.什么是闭包 闭包:闭包就是能够读取其他函数内部变量的函数;闭包简单理解成“定义在一个函数内部的函数”. 闭包的形式:即内部函数能够使用它所在级别的外部函数的参数,属性或者内部函数等,并且能在包含它 ...

  6. 理解 JavaScript 中的 this

    前言 理解this是我们要深入理解 JavaScript 中必不可少的一个步骤,同时只有理解了 this,你才能更加清晰地写出与自己预期一致的 JavaScript 代码. 本文是这系列的第三篇,往期 ...

  7. [译]Javascript中的闭包(closures)

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  8. JavaScript中的闭包理解

    原创文章,转载请注明:JavaScript中的闭包理解  By Lucio.Yang 1.JavaScript闭包 在小学期开发项目的时候,用node.js开发了服务器,过程中遇到了node.js的第 ...

  9. 深入理解javascript原型和闭包 (转)

    该教程绕开了javascript的一些基本的语法知识,直接讲解javascript中最难理解的两个部分,也是和其他主流面向对象语言区别最大的两个部分--原型和闭包,当然,肯定少不了原型链和作用域链.帮 ...

随机推荐

  1. Extract local angle of attack on wind turbine blades

    Extract local angle of attack on wind turbine blades Table of Contents 1. Extract local angle of att ...

  2. Microsoft 根证书计划弃用 SHA-1 哈希算法

    Microsoft 根证书计划弃用 SHA-1 哈希算法 微软官方2016年1月12日发布安全通报,自2016年1月1日起Microsoft 已经发布代码弃用变更,也就是说2016年1月1号后用SHA ...

  3. 【15】AngularJS 输入验证

    AngularJS 输入验证 AngularJS 表单和控件可以验证输入的数据. 输入验证 AngularJS 表单和控件可以提供验证功能,并对用户输入的非法数据进行警告.   客户端的验证不能确保用 ...

  4. noip模拟赛 小Y的问题

    [问题描述]有个孩子叫小 Y,一天,小 Y 拿到了一个包含 n 个点和 n-1 条边的无向连通图, 图中的点用 1~n 的整数编号.小 Y 突发奇想,想要数出图中有多少个“Y 字形”.一个“Y 字形” ...

  5. Linux下汇编语言学习笔记73 ---

    这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...

  6. xcode5下取消ARC

    打开你的工程,点击目录的工程文件,最顶端蓝色的,然后选择project下你的工程,还是蓝色那项,然后build Settings,然后往下拉,在Apple LLVM 5.0 - Language - ...

  7. java复习volatile关键字解析

    转自https://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受 ...

  8. - > 动规讲解基础讲解四——矩阵取数

    给定一个m行n列的矩阵,矩阵每个元素是一个正整数,你现在在左上角(第一行第一列),你需要走到右下角(第m行,第n列),每次只能朝右或者下走到相邻的位置,不能走出矩阵.走过的数的总和作为你的得分,求最大 ...

  9. Spring Cloud简介/版本选择/ZooKeeper例子搭建简单说明

    一.什么是Spring Cloud 官方的说法就是Spring Cloud 给开发者提供一套按照一定套路快速开发分布式系统的工具. 具体点就是Spring Boot实现的微服务架构开发工具.它为微服务 ...

  10. Javascript: 动态显示进度条

    {% if not config.exec_id == '' %} <br /> <div class="progress"> <div class= ...