这是大虾的第一篇博文,大虾试图用最直白的语言去描述出所理解的东西,大虾是菜鸟,水平有限,有误的地方希望路过的朋友们务必指正,谢谢大家了。

  从读书时代一路走来,大虾在学习的时候逐渐喜欢上了去追寻根源,这个东西到底是为什么?他有什么用处?他解决了什么问题?他是怎么被想到的?从这些问题当中,我们能够学到非常多,大虾深有体会。我相信,即使是这些东西在发明之时,就算是创始人也未必思考的这么周全,很多情况下,它一定是先遇到了什么实际问题之后,再去思考解决方案。也就是说,每一个新知识新东西的提出,一定是为着解决某个问题而出现的,否则他的存在就没有意义,而脱离了实际问题的学习也是没有意义的。在学习这个东西的过程中,大虾很看重的是,假如同样的是遇到了这个问题,大虾会怎么做去解决?别人又是怎么解决的?为什么别人的解决方案这么优秀?他是怎么想到的?我怎么就想不到呢?这种过程使大虾受益匪浅。

  话不多说,切入正题。本文主要介绍闭包。相信很多人只知道学闭包,直接去看他的原理,实现过程什么的,但是根据我的了解来看,初学者知道闭包的用途的并不多,为什么需要闭包?这些都不清楚,然而在大虾看来,这个非常重要,因为它对应着实际问题的解决,理论脱离了实践将变得毫无意义。

  话说二十年前,祖师爷创立js的时候,那时候页面并不复杂,大型的网页也不多,页面没什么js的时候,人们写页面时全局变量是随便定义的,直到某一天,随着页面js的增多,问题来了。拿一个模拟的alert来说,如果这么写:

var temp=a;
var abs=function(){};
var yourAlert=function(){};

  那么在这种情况下,全局变量temp,abs,yourAlert就被污染了。也就是说,如果要实现另外一个功能,比如说按钮btn,这个时候也需要写自己的代码,那么它的变量起名必须重新起名,必须避开上面的变量,否则就会把上面的内容给覆盖掉。这些新功能一旦数量很多,那么你的起名就必须避开所有的已用过的变量名,你必须挨个检查所有功能的变量名以保证他的不重复,这样就给开发带来很大的不方便。所以迫切的需要一种方法来避免它,来保护变量不被篡改污染。再者,在页面中,经常遇到多次调用的情况,同样以上面的alert为例,假如用户触发了10次alert,如果说每一次的触发都要重新创建一个alert的话,那样岂不是特别麻烦,特别消耗内存?这个时候同样需要一种能够反复调用的方法来优化代码的执行性能。 

  闭包应运而生。

  来看看闭包的实现过程,他到底是如何实现并且达到上述目的并解决实际问题的呢?深刻的理解整个本质的实现过程有助于我们的开发运用。

  当一个函数被调用时,一个执行环境(也称执行上下文)就会被创建(execution context),然而在js引擎内部,这个执行环境创建过程被分为了2个阶段:

    1、  建立阶段(此时还并没有执行具体的函数体的代码)

        建立变量对象(variable object):函数里面的arguments对象、函数参数、内部变量、函数声明

          1、  建立arguments对象,检查当前环境下的参数,建立该对象下的属性和属性值

          2、  检查当前环境下的函数声明:每找到一个函数声明,就会在变量对象里用函数名建立一个属性,属性值就是指向函数地址的引用。如果该函数名已经存在,那么其对应的属性值就会指向新的引用。

          3、  检查当前环境下的变量声明:每找到一个变量声明,就在变量对象下建立一个属性,其值为undefined(此时还未赋值)。如果该变量名已经存在,会直接跳过(防止指向函数的属性值被变量属性覆盖为undefined)

         建立作用域链

                当前变量对象被添加到执行环境的前端

         确定this的值

    2、  代码执行阶段

        执行函数中的代码,对变量赋值、函数引用、执行其他代码等等…

  具体的来看一个函数的代码:

 function f(x){
var a=20;
var b=function(){ };
function d(){
 
};
}
f(100);

  在调用f(100)的时候,执行环境的建立阶段发生如下变化:

fExecutionContext{ // f函数执行环境
  variableObject:{// f函数变量对象(对于函数来说,也称为活动对象AO)
    arguments:{
       0:100;
       length:1;
    },
    x:100,
    d:pointer to function d()//指向d函数的引用,实际上保存的是地址,它的顺序也在变量声明之上
     a:undefined,
     b:undefined, 
  },
  scopeChain:{....},//作用域链
  this:{...}// this值
}

  当上述建立阶段结束,js引擎立马进入执行阶段,一行一行的运行函数代码,给variableObject的属性赋值,执行阶段完成如下:

 fExecutionContext{ // f函数执行环境
  variableObject:{// f函数变量对象(对于函数来说,也称为活动对象AO)
    arguments:{
     0:100;
     length:1;
    },
    x:100,
d:pointer to function d()//指向d函数的引用,实际上保存的是地址,它(函数声明)的顺序也在变量声明之上
    a:20,
    b:pointer to function b(), 
  },
  scopeChain:{....},//作用域链
  this:{...}// this值
}

  事实上,如果这个环境是函数,变量对象并不能够被直接访问到,此时函数的活动对象(AO)将代替变量对象的角色。我们可以看到,上述环境中,执行环境的作用域链也被创建完成。

  那作用域链又是什么呢?事实上,在函数的创建时,会预先创建一个包含全局变量对象的作用域链。作用域链的本质是一个指向变量对象的指针列表!它只是引用而实际上并不包含变量对象!为了方便理解,可以把它想象成一根链条(实际上并不是真的存在这么一根链条),上面依次标记着顺序0,1,2,3.......,每一个数字对应着一个变量对象。在访问变量对象中的属性时,只能按照标记的顺序按0,1,2,3...的顺序依次访问,在这个链条的最前端,始终是当前执行的代码所在的环境的变量对象(可以理解为顺序标记为0),创建执行环境时,当前函数的活动对象将会被推入到作用域链的最前端!而下一个变量对象则来自其外部函数(顺序标记为1),再下一个则是外部函数的外部函数,全局执行环境的变量对象始终是链条的最后位置,全局的变量对象始终是最后一个被访问到。

  在本例中,以函数f为例,其作用域链关系如下图

  在寻找变量名和函数名的时候,会首先在顺序为0的位置的变量对象中寻找(也就是它自己的活动对象),如果找不到,就会向下一级变量对象寻找(函数的外部函数的变量对象,在作用域链中顺序标记为1),一直搜索到最后一个对象——全局环境的变量对象为止。全局环境的变量对象始终存在于每一个作用域链中!当函数执行完毕后,函数的活动对象就会被销毁,内存中仅保存全局变量对象。

  然!而!且!慢!闭包的情况不一样啊!

  看下面这个超级简单的例子。

 function f(){
var a=20;
return function d(){
  a--;
};
}
var sB=f();
sB();

  上面的d函数是直接定义在函数f的内部中的,即是说函数f是函数d的外部函数,按照上面所述原则,在函数d的作用域链中,函数f的变量对象会被添加进函数d的作用域链中!此时函数d的作用域链一共有3个对象:函数d的活动对象会被标记为0,函数f的变量对象会被标记为1,全局变量对象标记为2,访问时按照0,1,2的顺序访问,寻找变量时先在自己的活动对象里找,再去顺序为2即函数f里面找,这样就能访问外部函数f中的所有变量。然而当函数f执行完毕时,它的执行环境的作用域链会被销毁,但它的活动对象并没有被销毁,仍然保存在内存中,匿名函数d的作用域链仍然在引用着这个活动对象,直到匿名函数d被销毁,函数f的活动对象才会被销毁。这就是闭包的最大的不同之处!

  我们来画个流程图,理解下闭包过程中所发生的变化。从函数的创建开始。

  前方高能,多图预警!请在wifi下点开,土豪请无视。

1、函数f的创建

2、函数f开始执行了

3、执行到 return语句

4、执行var sB=f();

5、调用函数d

6、执行完毕

  这就是闭包的整个实现过程,闭包实现后,可以在全局反复调用内部函数d(),此时在即使全局定义相同的变量a,调用函数时,使用的值仍然是函数f的活动对象里面的值,外面的更改无法影响到局部变量a。这里只是做个简单的介绍,闭包还有很多应用情况,实际情况也更加复杂,还有很长的路要走。

  参考书籍:《JavaScript高级程序设计第3版》

  参考内容: http://tieba.baidu.com/p/2348703848

        http://blogread.cn/it/article/6178?f=sa 

 

JavaScript之一: 闭包、执行环境、作用域链的更多相关文章

  1. JavaScript:理解执行环境、作用域链和活动对象

    作用域的原理,对JS将如何解析标识符做出了解答.而作用域的形成与执行环境和活动对象紧密相关. 我们对于JS标识符解析的判断,存在一个常见误区 首先,看一个关于JS标识符解析的问题 ,源于风雪之隅提出的 ...

  2. 【JS】JavaScript中的执行环境与作用域

    JavaScript中的执行环境定义了变量或函数有权访问的数据(每个函数都有自己的执行环境),全局执行环境是最外围的执行环境,在浏览器中,全局执行环境就是window对象,所以所有的全局变量和函数都是 ...

  3. 闭包&执行环境和作用域

    闭包 执行环境和作用域参考:<javascript高级程序设计(第3版)>4.2节

  4. javascript基础进阶——执行环境及作用域链

    概念 执行环境 执行环境定义了变量或函数有权访问的其他函数,决定了他们各自的行为.每个执行环境都有一个与之关联的变量对象. 变量对象 环境中定义的所有变量和函数都保存在这个对象中. 全局执行环境 全局 ...

  5. JavaScript语言精粹--执行环境及作用域,this

    1.执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为. 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中. 虽然我们无法访问,但是解析器在处理数据时 ...

  6. Javascript高级程序设计——执行环境与作用域

    Javascript中执行环境是定义了变量或函数有权访问的其他数据,决定了各自的行为,每个执行的环境都有一个与之关联的变量对象,环境中定义的所以变量和函数都保存在这个对象中. 全局执行环境是最外围的一 ...

  7. Javascript 函数及其执行环境和作用域

    函数在javascript中可以说是一等公民,也是最有意思的事情,javascript函数其实也是一个对象,是Function类型的实例.因此声明一个函数首先可以使用 Function构造函数: va ...

  8. javascript学习笔记 - 执行环境及作用域

    一 执行环境(环境) 1.每个执行环境都有一个关联的全局变量对象.例如:web浏览器中,window对象为全局变量对象.环境中定义的所有变量和函数都保存在该对象中.全局执行环境是最外围的环境. 2.执 ...

  9. 对JS闭包和函数作用域的问题的深入讨论,如何理解JS闭包和函数作用域链?

    首先先引用<JavaScript权威指南>里面的一句话来开始我的博客:函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的. 因此,就出现了如下的几串代码: ...

  10. javaScript的闭包 js变量作用域

    js的闭包 js的变量作用域: var a=90; //定义一个全局变量 function test(){ a=123; //使用外层的 a变量 } test(); document.write(&q ...

随机推荐

  1. WPF换肤之三:WPF中的WndProc

    原文:WPF换肤之三:WPF中的WndProc 在上篇文章中,我有提到过WndProc中可以处理所有经过窗体的事件,但是没有具体的来说怎么可以处理的. 其实,在WPF中,要想利用WndProc来处理所 ...

  2. python学习笔记之二:使用字符串

    这里会介绍如何使用字符串格式化其他的值,并了解一下利用字符串的分割,连接,搜索等方法能做些什么. 1.基本字符串操作 所有标准的序列操作(索引,分片,乘法,判断成员资格,求长度,取最大值和最小值)对字 ...

  3. Java 理论与实践: 处理 InterruptedException(转)

    很多 Java™ 语言方法,例如 Thread.sleep() 和 Object.wait(),都可以抛出InterruptedException.您不能忽略这个异常,因为它是一个检查异常(check ...

  4. jquey :eq(1)

    $("#div_Goods .datagrid-row .numberbox:eq(1)") $("#div_Goods .datagrid-row .numberbox ...

  5. Java 并发专题 :闭锁 CountDownLatch 之一家人一起吃个饭

    最近一直整并发这块东西,顺便写点Java并发的例子,给大家做个分享,也强化下自己记忆. 每天起早贪黑的上班,父母每天也要上班,话说今天定了个饭店,一家人一起吃个饭,通知大家下班去饭店集合.假设:3个人 ...

  6. XMPP我写底层协议(零)--废话和准备开幕前

    当我想写一个非常早期的一点总结.但总是忙没有时间停止做这样的事情. 秦与我的兄弟之前说的,这并不是说我没开灵.但是,因为很多事情还没有时间来写blog. 我没有完全理解,真到自己在这个位置上的时间,能 ...

  7. CMake入门(二)

    CMake入门(二) 最后更新日期:2014-04-25 by kagula 阅读前提:<CMake入门(一)>.Linux的基本操作 环境: Windows 8.1 64bit英文版.V ...

  8. 查看linux系统版本号命令

    一.查看内核版本号命令: 1) [root@SOR_SYS ~]# cat /proc/version Linux version 2.6.18-238.el5 (mockbuild@x86-012. ...

  9. json学习初体验--第三者jar包实现bean、List、map创json格式

    1.的需要jar包裹json-lib.jar 下载链接: http://sourceforge.net/projects/json-lib/files/json-lib/ 此包还须要下面的依赖包, c ...

  10. 微信电脑版(Mac和Windows)安装

    内容简介 1.微信Windows版 2.微信Mac版 3.总结优势 微信电脑版 众所周知,腾讯公司(马化腾先生执掌的巨头公司)开发的超成功App:微信.一经推出便引发业界轰动,使用人数更是直逼QQ. ...