过很多谈如何理解闭包的方法,但大多数文章,都是照抄或者解释《Javascript高级程序设计(第三版)》对于闭包的讲解,甚至例程都不约而同的引用高程三181页‘闭包与变量’一节的那个“返回数组各个项,结果各个项的值都相同”的例程,还有些文章的讲解过程上一步与下一步之间的跨度简直就是一步登天,让人反复看半天都无法理解。

闭包的理解需要很多概念做铺垫,包括变量作用域链、执行环境、变量活动对象、引用式垃圾内存收集机制等,如果对本文涉及的这些概念不理解,可以去找本《Javascript高级程序设计(第三版)》好好看看并理解这些概念。

我自己也是在反复编写一些例子并仔细认真的阅读和理解高程三与闭包相关的概念和知识后,终于对闭包有了一定的理解,为了检验学习效果,特别写篇文章以便检验自己是否真正理解和掌握。

在理解闭包之前,我们先来看些理解闭包容易忽略的小东西。

//例子1:
function foo(){
var a = 1;
return a;
};
console.log(foo()); //

上面的这个例子很好理解,一个函数如果return一个值,那么这个函数就可以当作return的值直接使用,无论它return的是一个引用类型(函数、数组)还是基本数据类型。

//例子2:
function foo(){

    function foos(){
var b = 2;
return b;
} };
console.log(foos()); //Uncaught ReferenceError: foos is not defined

这个例子说明,如果一个函数B被声明在一个函数A的内部,那么在函数A外部,通过函数B的函数名直接调用函数B,是会出未定义错误的。你可以理解为:变量作用域规则中的局部作用域规则对于函数声明同样成立。

//例子3:
function foo(){

    var a = 1;

    function foos(){
return a;//注意:返回的a是foo中定义的变量,而不是foos中定义的变量。
} console.log(foos());
}; foo();//

例子3说明foos只能在foo内部执行,这符合我们对于“变量作用域规则中的局部作用域规则,对于函数声明同样成立”的理解。

//例子4:
function foo(){
var arr = new Array();
for(var i=0;i<10;i++){
arr[i] = function(){
return i;
};
}
return function(){
for(var k = 0;k<arr.length;k++){
console.log(arr[k]());
}
};
}; console.log(foo()());//输出10个10;

例子4中中执行foo()以后得到的是foo自己返回的一个匿名函数(如下所示):

return function(){
for(var k = 0;k<arr.length;k++){
console.log(arr[k]());
}
};

那么,此时我们还需要执行一遍这个得到的匿名函数,才能得出最后的值(也就是:10个10),而执行这个得到的匿名函数(我们不妨叫它函数C)时,foo已经执行了一遍了,这个时候foo的变量i的值已经是10了,由于变量对象是通过引用赋值的,所以这个时候foo内的闭包函数(也就是引用了foo的变量i的匿名函数)再来引用foo的变量i,那么得到的值自然就是10了。这里的关键在与foo内的闭包函数执行的时机,如果闭包函数立即执行后再赋值给arr[i],那么就可以得到我们期望的值0到9了;

//例子5:
function foo(){
var arr = new Array(); for(var i=0;i<10;i++){ arr[i] = function(){
return i;
}();
}
return function(){
for(var k = 0;k<arr.length;k++){
console.log(arr[k]);
}
};
}; console.log(foo()());//0到9

关于例子4更详细的解释:
在例子4中,根据javascript的引用垃圾收集机制(这个机制的规则是:存在大于0个引用的变量不应该被销毁),虽然此时函数foo已经执行完了,但是foo的这个变量i因为被匿名函数(我们不妨叫它函数B)引用,所以这个i还是没有被垃圾收集程序销毁,那么这个i此时存在哪里呢?肯定不是存在于函数foo的执行环境里面,因为执行环境创建和销毁的规则是:当一个函数创建时,则创建这个函数的执行环境,一旦这个函数执行完毕则马上销毁这个执行环境。因此这个i只可能存在于foo内部的匿名函数(函数B)的执行环境中,这个函数B的执行环境中不但有自己的作用域和自己的变量对象,而且包含了其外部函数foo的作用域和foo的变量对象,变量i就存在于函数B包含的foo的作用域中和变量对象中。由于javascript不能直接操作内存空间,所以javascript只能以引用的方式访问变量对象,当foo的变量i的值已经变成10了的时候,再去执行foo的闭包函数(函数B),那么此时对i的引用自然会得到10。

使用以上实验得出的原理,我们还可以很容易的理解《Javascript高级程序设计(第三版)》181页最下面的那个例子:

//《Javascript高级程序设计(第三版)》181页最下面的那个例子:

function createFunctions(){
var result = new Array(); for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
}
}(i);//这里是将参数传给该匿名函数,并且立即执行该匿名函数的写法;
}
return result;
} for(var i=0;i<10;i++){
console.log(createFunctions()[i]());
}//0,1,2,3,4,5,6,7,8,9

同时,我们可以很容易的改写这个例子:

function createFunctions(){
var result = new Array(); for(var i=0;i<10;i++){
result[i] = function(){
return function(){
return i;
}()//这里添加了立即执行
}();//这里是将参数传给该匿名函数,并且立即执行该匿名函数的写法;
}
return result;
} for(var i=0;i<10;i++){
console.log(createFunctions()[i]);//这里去掉了一对括号
}//0,1,2,3,4,5,6,7,8,9

这个改写的例子同时证明,最内层的匿名函数还是可以引用最外面全局函数的变量。

下面这张图片中的例子,使用闭包就可以很好的理解了:

要是实在不理解,还可以试着这样来验证一下帮助理解:

function foo(){

   var diss = "mem_count";

   return function(){

       return diss;

   }

}

var fo = foo();

var as = fo();
//undefined
as
//"mem_count"
var as2 = fo();
//undefined
as2
//"mem_count"
fo()
//"mem_count"
fo()
//"mem_count"
fo()
//"mem_count"

如果这样还是没办法理解,那真是应该先去看看变量的作用域、垃圾回收机制,函数表达式等基础知识了。

javascript 闭包的理解(一)的更多相关文章

  1. javascript闭包的理解

    闭包是Javascript的一个难点,但也是一个很重要的知识点. 1.首先我们要知道变量作用域链 变量的作用域分两种:全局变量和局部变量.没有定义到任何函数中的变量为全局变量,在函数中定义的变量为局部 ...

  2. 我对 javascript 闭包的理解

    学js的学到闭包,但是理解不深. 后来看了一下这篇文章: 地址:http://leepiao.blog.163.com/blog/static/4850313020112835355917/ 内容如下 ...

  3. 对JavaScript闭包的理解

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 在开始了解闭包前我们必须要先理解JavaScript的变量作用域. 一.变量的作用域无非就是两 ...

  4. 关于Javascript 闭包的理解

    一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话 ...

  5. 关于Javascript闭包的理解

    以下内容属个人理解,如有看不明白或漏洞之处,纯属水平不佳,还望见谅. 关于闭包,高程里的定义是:指有权访问另一个函数作用域中的变量的函数.创建闭包最常见的方法就是在一个函数的内部再创建一个函数. 这里 ...

  6. javascript闭包的理解和实例

    所谓闭包,值得是词法表示包括不必要计算的变量的函数,也就是说,该函数可以使用函数外定义的变量. 顺便提示一下: 词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通 ...

  7. Javascript闭包简单理解

    提到闭包,想必大家都早有耳闻,下面说下我的简单理解.平时写代码.第三方框架和组件都或多或少用到了闭包.所以,了解闭包是非常必要的.呵呵... 一.什么是闭包简而言之,就是能够读取其他函数内部变量的函数 ...

  8. javascript 闭包的理解(二)

    // 定义一个User构造函数 function User(properties){ //遍历对象属性,确保它作用域正确 for(var i in properties){ (function(whi ...

  9. javascript 闭包的理解

    1 需要明白概念: 执行环境 变量对象,活动对象 作用域,作用域链 闭包 垃圾处理机制 闭包陷阱

随机推荐

  1. ansible基础☞第一条命令

    我的两个测试机: 系统: ubuntu 16.04.2 ansible-master: 192.168.0.107 ansible-slave: 192.168.0.108 ansible版本: ro ...

  2. 4.2.1 Data Flow-File Write-基本过程

  3. HDFS 通信接口

  4. git 本地仓库操作

    一.git对象模型和存储 二.常用命令 1)git checkout branch 切换分支 假设现在有两个分支,master和dev分支 i dev分支上没有readme.txt 在master分支 ...

  5. AtCoder Beginner Contest 078 D ABS

    光做C了,做完C,就要结束了,看了看D,没看懂那操作啥意思,就扔了. 刚才看了看,突然懂了.. 就是每个人从那堆牌上边拿牌,最少拿一张,最多可以全拿走,然后手里留下最后一张拿到的,其余的都扔掉. 比如 ...

  6. day7_python之面向对象item系列(__getitem__,__setitem__,__delitem__)

    class Foo: def __getitem__(self, item): print('=====>get') return self.__dict__[item] def __setit ...

  7. 第三期 预测——Frenet 坐标

    Frenet坐标 在讨论过程模型之前,我们应该提到“Frenet Coordinates”,它是一种以比传统x,y笛卡尔坐标更直观的方式表示道路位置的方式. 用Frenet坐标,我们使用变量 s和d描 ...

  8. [转][ASP.NET Core 3框架揭秘] 跨平台开发体验: Windows [下篇]

    由于ASP.NET Core框架在本质上就是由服务器和中间件构建的消息处理管道,所以在它上面构建的应用开发框架都是建立在某种类型的中间件上,整个ASP.NET Core MVC开发框架就是建立在用来实 ...

  9. 【原生JS】层叠轮播图

    又是轮播?没错,换个样式玩轮播. HTML: <!DOCTYPE html> <html lang="en"> <head> <meta ...

  10. HDU 5974"A Simple Math Problem"(GCD(a,b) = GCD(a+b,ab) = 1)

    传送门 •题意 已知 $a,b$,求满足 $x+y=a\ ,\ LCM(x,y)=b$ 条件的 $x,y$: 其中,$a,b$ 为正整数,$x,y$ 为整数: •题解 关键式子:设 $a,b$ 为正整 ...