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

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

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

  话说二十年前,祖师爷创立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、  代码执行阶段

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

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

1 function f(x){
2   var a=20;
3   var b=function(){
4
5   };
6   function d(){
7  
8    };
9 } 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 

 

转载于:https://www.cnblogs.com/liudaxia/p/4859366.html

JavaScript之一: 闭包、执行环境、作用域链相关推荐

  1. js执行环境作用域和闭包_JavaScript中执行上下文,提升,作用域和闭包的终极指南

    js执行环境作用域和闭包 It may seem surprising, but in my opinion the most important and fundamental concept to ...

  2. JavaScript中的执行环境

    JavaScript中的执行环境 注意区分函数的执行环境和函数声明环境 看代码: 转载于:https://www.cnblogs.com/mc67/p/5190179.html

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

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

  4. 作用域链涉及了什么计算机底层知识,你必须知道的Javascript知识点之深入理解作用域链的介绍...

    示例代码: var xxxVar1 = 1; var outer = function(){ var xxxVar2 = 2; var results = []; for(var i = 0; i&l ...

  5. php函数嵌套 作用域,javascript 嵌套的函数(作用域链)_javascript技巧

    嵌套的函数(作用域链) 当你进行函数的嵌套时,要注意实际上作用域链是发生变化的,这点可能看起来不太直观.你可把下面的代码置入firebug监视值的变化. var testvar = 'window属性 ...

  6. JS高级——函数执行、作用域链内存结构图

    一.JavaScript的执行过程 假如我们有下面一段代码,它在JavaScript中是如何被执行的呢? 1.1 第一步:初始化全局对象 js引擎会在执行代码之前,会在堆内存中创建一个全局对象:Glo ...

  7. 串讲-解释篇:作用域,作用域链,执行环境,变量对象,活动对象,闭包

    这篇接:理论篇:作用域,作用域链,执行环境,变量对象,活动对象,闭包 看例子: function compare(value1, value2) {if (value1 < value2) {r ...

  8. 闭包,作用域链,垃圾回收,内存泄露

    关于闭包,我翻了几遍书,看了几遍视频,查了一些资料,可是还是迷迷糊糊的,干脆自己动手来个总结吧 !欢迎指正... (- o -)~zZ 1. 什么是闭包? 来看一些关于闭包的定义: 闭包是指有权访问另 ...

  9. javascript:闭包的总结

    *前言:这次总结闭包,分别参考了<js高级程序设计>.廖雪峰老师的网站.还有<js忍着秘籍>,好了,废话少说,黑喂狗--- ---------------------严肃分割线 ...

  10. JavaScript重难点解析4(作用域与作用域链、闭包详解)

    JavaScript重难点解析4(作用域与作用域链.闭包详解) 作用域与作用域链 作用域 作用域与执行上下文 作用域链 闭包 闭包理解 将函数作为另一个函数的返回值 将函数作为实参传递给另一个函数调用 ...

最新文章

  1. 云端应用SQL注入攻击
  2. girton college map
  3. 切片器可以设置日期格式?_Power BI 中的切片器
  4. 2019蓝桥杯省赛---java---C---9(等差数列)
  5. 上海交大计算机学院奖学金,上海交通大学-电子信息与电气工程学院-学生工作办公室...
  6. android 安装assets中的apk,如何安装assets下apk,附源码(原创)
  7. Python是什么类型的语言?
  8. 深度学习经典算法 | 蚁群算法解析
  9. 【Gym-101889 D】Daunting device【分块】
  10. 计算机固态硬盘安装,电脑安装了固态硬盘,需要如何重装系统?详细的方法教程在这里!...
  11. xp访问贡享显示指定服务器,XP访问Win10共享打印机指定的网络名不再可用解决方法...
  12. 《遥远的救世主》遵守客观规律(四)——文化属性
  13. Ht7038 三相电能计量芯片 测量6路电流。
  14. 使用java的方式配置Spring---JavaConfig
  15. linux 中的rime 输入法 自定义 新世纪五笔输入法
  16. jfinal调用mysql存储过程 封装_jfinal 调用存储过程
  17. elementUI表格自动刷新
  18. 如何用切片工具做html网页,ps中的切片工具怎么用,怎么将html文本添加到切片...
  19. UVA 11384 Help is needed for Dexter (递归函数)
  20. 我的中软国际实习Day16

热门文章

  1. memcached—如何在Windows操作系统中安装、启动和卸载memcached
  2. VScode 安装与简单配置(转载)
  3. MySQL 一条SQL语句执行得很慢的原因有哪些?
  4. 时间复杂度与空间复杂度小结
  5. 一个Http请求的流程
  6. 航空信息指挥调度系统_应急指挥中心指挥调度系统解决方案(一)
  7. puml绘制思维导图_免费在线思维导图神器 简单又漂亮 比Wodrd好用很多
  8. arnold官方帮助文档_Python用不好英语水平不够?这里有官方中文文档你看不看
  9. 最长双重叠字符串java_java – 重复但重叠的字符串的算法
  10. java tmp 目录_Tmp目录丢失引发Java进程异常