作用域链与闭包

  读了这篇博文地址后,对作用域和闭包终于有一了些了解。之前看各种文章,让我以为闭包是因为内部引用变量,导致变量无法在外部访问,而通过内部函数可以被外部访问导致被引用的变量可以间接访问。现在看来,这只能说是闭包的一种外在表现,跟闭包本身没有任何关系的。

  下面总结一下作用域链和闭包。

1. 执行至闭包之前的整个过程

  更为详细的可参见那篇博文,这里只是简单总结下。在浏览器执行解析代码时,分为编译和代码执行两个部分。编译阶段负责对语法解析,作用域链分析,语法优化。代码执行阶段中,先为函数创建上下文,然后执行代码。创建函数上下文时,要进行:创建变量对象、创建作用域链、确定this的指向。

  变量对象(VO)创建过程:建立arguments对象,并将数据挂载到到arguments上 -> 针对函数内部声明的函数、变量,先进行挂载声明的函数,然后挂载声明的变量(如果名称冲突则不覆盖)

变量对象在函数执行之前是不可访问的,执行代码时,变量对象转变为活动对象,所有变量获取均从活动对象中取得,如果没有,则会抛异常。

2. 作用域链创建与闭包

作用域链的生成规则是在编译阶段就确定了的。当函数创建上下文时,除创建变量对象外,还会产生一个闭包(closure)。这个闭包会在子函数中被加入到作用域链中。某个函数的作用域链是这个样子的:

当前函数上下文的活动变量 - 上层闭包 - 上上层闭包 .....

因此闭包的概念应该是这样的:在某个函数内声明了另外一个函数(子函数),子函数(或子函数的子函数)引用了当前函数声明的一些变量或函数,这些变量或函数由于在子函数(或子函数的子函数)的作用域中会被访问到,因此这些变量或函数的访问地址被保存到了一个对象中,这个对象就是一个闭包。

  闭包的实际作用应该为子函数的函数作用域链提供一个可访问的对象,即使当前的函数已经从栈中移出,由于子函数中已经将该闭包加到了作用域链中(子函数的子函数可继承使用子函数的所有闭包),子函数所有的变量或函数都是从作用域链中获取的,因此子函数从表现上看仍旧可以访问在父函数或父父函数中声明的变量(实际上访问的是作用域链上的变量)。

  这样,函数的作用域链的规则应该是这样的:

当前函数上下文的活动变量 - 上层函数的闭包 - ...上层函数拥有的闭包

这样一看就清晰明了了吧。

3. 理论验证

  为验证这个推论,建立一个带有四层函数的脚本。其中第一层中声明变量a,被second、four引用, cuse变量仅被four引用,unuse未被任何子函数引用。

(function () {
  var a = 1
  var cuse = 2
  var unuse = 10

  function second () {
    var b = a

    function third() {
      var d = ‘xx‘

      function four () {
        var c = b
        c += a
        c += cuse
        console.log(c)
      }
      four()
      console.log(d)
    }
    third()
  }

  second()

  var final = unuse + 1
  return final
})()

  1. 执行到second时,看到匿名函数的closure中只有a和cuse,但second中未引用匿名函数声明的任何一个变量,说明这个closure应该是在编译阶段就确定了哪些变量会被加入到closure中。

  2. 执行到third函数时,closure中出现了匿名函数的closure和second的closure。

  3. 执行到four时,出现了匿名函数的closure和second的closure。由于third中声明的变量没有被子函数引用,它未产生closure,在four中也就没有出现closure。

  从上面的结果看出:编译阶段已经确认了哪些变量会被加入到closure中,这个closure会被加入到所有子函数的作用域链中。换种理解就是子函数的使用域链中closure构成是由上层函数生成的closure以及它所拥有的所有closure组成的。

  当使用eval函数时,会有更明显的对比(见下图):使用eval后,父函数声明的所有变量和函数都被加入到closure中。这是因为eval执行具有不确定性,为保证变量可以访问,只能将所有变量加入closure中,避免代码执行过程中找不到变量的问题(arguments之所以被加入到closure中,是因为新的箭头函数导致子函数会使用父函数的arguments对象)。

4. 总结

  闭包的概念还是比较难以理解的,要从浏览器解释执行代码的角度理解才会更深刻(貌似这不是写代码的考虑的,不知道为啥面试全会问)。感谢·这波能反杀·博主的详细讲解,一对比文章质量,我写的估计只有理解的人能快速看明白,有点像写论文,但写的匆忙质量差,还是要多练习。继续加油吧~~

附: 博文地址 https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/si-3001-zuo-yong-yu-lian-yu-bi-bao.html

时间: 2024-08-28 09:16:40

作用域链与闭包的相关文章

JavaScript中的作用域链和闭包

JavaScript中的作用域链和闭包 2012-06-29 11:41 1878人阅读 评论(46) 收藏 举报 JavaScript中出现了一个以前没学过的概念——闭包.何为闭包?从表面理解即封闭的包,与作用域有关.所以,说闭包以前先说说作用域. 作用域(scope) 通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突. 全局作用域(Global Scope) 在代码中任何地方都能访问到的对象拥

作用域链、闭包和原型链

Function:  匿名函数,作用域,作用域链和闭包 函数的重载:  什么是:函数名相同,参数列表不同.根据传入函数的参数的不同,整形不同的逻辑. 何时用:如果一项任务,根据不同的参数,不执行不用的逻辑. 优点:减轻调用者的负担.  问题:js语法不知函数的重载. 解决办法:在函数中都有arguments的属性,专门用于接收传入函数的所有参数值的类数组对象. 匿名函数:  什么是:在函数定义时,不给函数名,即不被任何变量引用. 何时用:确定函数只使用一次.  优点:节约内存. 如何用:1.自调

个人理解的javascript作用域链与闭包

闭包引入的前提个人理解是为从外部读取局部变量,正常情况下,这是办不到的.简单的闭包举例如下: 1 function f1(){ 2 3 n=100; 4 5 function f2(){ 6 alert(n); 7 } 8 9 return f2; 10 11 } 12 13 var result=f1(); 14 15 result(); // 100 代码中的f2函数,就是闭包. 1 function f1(){ 2 3 var n=100; 4 5 nAdd=function(){n+=1

JS详细图解作用域链与闭包

JS详细图解作用域链与闭包 攻克闭包难题 初学JavaScript的时候,我在学习闭包上,走了很多弯路.而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战. 闭包有多重要?如果你是初入前端的朋友,我没有办法直观的告诉你闭包在实际开发中的无处不在,但是我可以告诉你,前端面试,必问闭包.面试官们常常用对闭包的了解程度来判定面试者的基础水平,保守估计,10个前端面试者,至少5个都死在闭包上. 可是为什么,闭包如此重要,还是有那么多人没有搞清楚呢?是因为大家不愿意学习吗?还真不是,

在chrome开发者工具中观察函数调用栈、作用域链与闭包

在chrome开发者工具中观察函数调用栈.作用域链与闭包 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象,闭包,this等关键信息的变化.因此,断点调试对于快速定位代码错误,快速了解代码的执行过程有着非常重要的作用,这也是我们前端开发者必不可少的一个高级技能. 当然如果你对JavaScript的这些基础概念[执行上下文,变量对象,闭包,this等]了解还不够的话,想要透彻掌握断点调试可能会有一些困

js内存空间 执行上下文 变量对象详解 作用域链与闭包 全方位解读this

内存空间:https://blog.csdn.net/pingfan592/article/details/55189622 执行上下文:https://blog.csdn.net/pingfan592/article/details/55189804 变量对象详解:https://blog.csdn.net/pingfan592/article/details/56009330 作用域链与闭包:https://blog.csdn.net/pingfan592/article/details/5

1--面试总结-js深入理解,对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This

参考一手资料:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/中文翻译版本:https://zhuanlan.zhihu.com/p/32042645 Javascript 是一种单线程编程语言,这意味着它只有一个调用栈,call Stack(调用栈 ,,先入后出) 核心:对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This js原型链? 定义 原型对象也是简单的对象并且可以拥有它们自

JavaScript中的作用域 、作用域链和闭包

JavaScript中作用,作用域链和闭包详解 一.作用域在js中有全局变量和局部变量之分:比如var a = 1;function sum(){var b=1console.log(b) //1console.log(a) //2 }sum()console.log(a) //3console.log(b) //4 例子中 a 是全局变量,b是局部变量(定义在函数内部,只能在函数内部访问)所以第1行正确 函数内部也能访问全局变量 a所以第2行也能正确 第三行也正确.第4行有外部不能访问内部变量

作用域、作用域链、闭包

作用域.作用域链 一.Js以前没有块级作用域,不过在ES6中有let了. 二.Js使用函数作用域 function aaa(){ var a = "a"; } console.log(a);//报错 三.声明提前 console.log(aaa)//报错 console.log(aaa);//undefined 声明未定义 var aaa; console.log(aaa); //undefined 声明未定义 var aaa = "aaa"; 四.Js的作用域链