Home  >  Article  >  Backend Development  >  阮一峰关于 Javascript 中闭包的解读是否正确?

阮一峰关于 Javascript 中闭包的解读是否正确?

WBOY
WBOYOriginal
2016-06-06 16:22:592134browse

阮一峰有一篇关于javascript 闭包的科普文章:
学习Javascript闭包(Closure)

感觉讲的挺好的,考虑到之前被朴灵喷的很惨,所以开始怀疑博文中的讲解是否正确。

博文部分观点:
1. 我的理解是,闭包就是能够读取其他函数内部变量的函数。
2.由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
3.闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

回复内容:

某些說法的確不嚴謹,但也沒必要噴。
比如:
函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
不使用 var 等於沒有聲明變量,而是相當於訪問了 window 的屬性。
和在 window 下聲明變量的區別在於,訪問 window 的屬性創建的變量可以 delete,在全局作用域下直接聲明的變量不可以 delete。

<code class="language-js"><span class="nb">window</span><span class="p">.</span><span class="nx">b</span> <span class="o">=</span> <span class="s2">"hi"</span><span class="p">;</span>

<span class="nx">b</span><span class="p">;</span> <span class="c1">// "hi"</span>

<span class="k">delete</span> <span class="nx">b</span><span class="p">;</span> <span class="c1">// true</span>

<span class="nx">b</span><span class="p">;</span> <span class="c1">// ReferenceError: b is not defined</span>

<span class="p">(</span><span class="kd">function</span><span class="p">(){</span><span class="nx">c</span> <span class="o">=</span> <span class="s2">"hi"</span><span class="p">}());</span>

<span class="nx">c</span><span class="p">;</span> <span class="c1">// "hi"</span>

<span class="k">delete</span> <span class="nx">c</span><span class="p">;</span> <span class="c1">// true</span>

<span class="nx">c</span><span class="p">;</span> <span class="c1">// ReferenceError: c is not defined</span>

<span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="s2">"hi"</span><span class="p">;</span>

<span class="nx">a</span><span class="p">;</span> <span class="c1">// "hi"</span>

<span class="k">delete</span> <span class="nx">a</span><span class="p">;</span> <span class="c1">// false</span>

<span class="nx">a</span><span class="p">;</span> <span class="c1">// "hi"</span>
</code>
以 “某些說法的確不嚴謹,但也沒必要噴。” 开头的匿名回答是对的。

关于闭包的概念,其实就是来自函数式语言。

<code class="language-text">var a = function () {
  var test = {};
  setTimeout(function () {
    console.log(test);
  }, 1000);
}
</code>
阮一峰的文章是他的学习笔记,并不保证全部正确,因为只是他的理解,而且这篇文章是他09年时写的,可能那时候他的水平比现在还差一些。
不过他文章的含金量还是比较高的,同时因为是他的学习笔记,所以我们读起来也通俗易懂。很多初学者可以从阮一峰的文章向高水平进阶,但是通俗易懂的东西一般会存在不严谨的情况。严谨的文章非常好,但是不利于初学者理解。为什么这么说呢,严谨的概念会包含很多对初学者来说十分模糊的专业术语,比如上面答主cosx所提到的:閉包是由函式和與其相關的參照環境組合而成的實體。(原句可能是:A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).)如果是初学者,我相信一看到函式、相关环境等字眼,则直接懵逼。
但是阮一峰说:闭包就是能够读取其他函数内部变量的函数。这么解释,只要动动脑,应该可以大概理解闭包是什么东西了。即便这句话很不严谨。
所以说阮一峰的文章依然值得看,他的文章可以把你带入一个领域,但是想要正确的认识这个领域,你依然需要去看书,这也是网络文章与实体书的区别。
----------------------------------------------
想要彻底正确的理解闭包,还是要看cosx、朴灵的回答,但是要攒够基础知识。 闭包就是在函数中使用未在函数中定义但在函数所处上下文有效的变量(标识符)与函数本体的集合。 还是严谨一点比较好 闭包确实是js最难理解的东西了,闭包的本质可以说是作用域链形成的
要理解闭包,必须要理解这几个概念
1.词法作用域
2.执行上下文
3.活动对象
4.scope属性

假设如下代码
function A(arg1){
var v1=1;
var b=function B(){ //执行到这一行定义了函数B,用表达式避免变量提升问题
}
}
A();
一个函数B在定义(声明)时,它在代码树上的上一层函数A此时正处于运行状态(执行上下文指向函数A),此时函数B会生成一个scope属性(函数本质也是对象,可以定义属性),B.scope是一个数组,但是通过代码是访问不到的,通过chome调试面板可以看到,留心的同学可以发现,push了A的活动对象(如果A有上一层函数,会依次push进去),此时B.scope=[{v1:1}],当前函数的活动对象包含了一个函数体内所有var声明的变量,在函数B内部查找变量时优先查找B的活动对象中的变量,如果没有,则遍历scope属性数组中的父级函数活动对象。

因为只有在运行时的代码才能声明函数,每一个函数在声明时,父级函数必然处于执行状态,递归思考一下,可知作用域链完全由函数编写时的嵌套顺序所决定,这就是词法作用域的含义,看一个函数的作用域,只要静态分析代码树的结构就行了。
所谓的作用域链,其实就是隐含的scope属性一级级的向上引用上一层函数的变量对象,变量对象的查找过程,原理上和对象的属性通过__proto__原型链查找过程差不多。 闭包就是打入敌人内部的间谍,通过他你可以接触敌人对外封锁的信息。 再提一些个人认为有误导性的地方!

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
搞的好像其他面向对象的语言不能似的。

另一方面,在函数外部自然无法读取函数内的局部变量。
根本没有提到关键,为什么不能读取内部变量?函数在执行完后会销毁其内部环境这个就没提及。

第六个思考题:
如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。
<code class="language-text">var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());
</code>
什么是闭包?

闭包并不是什么新奇的概念,它早在高级语言开始发展的年代就产生了。闭包(Closure)是词法闭包(Lexical Closure)的简称。对闭包的具体定义有很多种说法,这些说法大体可以分为两类:

  • 一种说法认为闭包是符合一定条件的函数,比如参考资源中这样定义闭包:闭包是在其词法上下文中引用了自由变量(注1)的函数。
  • 另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。比如参考资源中就有这样的的定义:在实现深约束(注2)时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体被称为闭包。

这两种定义在某种意义上是对立的,一个认为闭包是函数,另一个认为闭包是函数和引用环境组成的整体。虽然有些咬文嚼字,但可以肯定第二种说法更确切。闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。其中的约束是指一个变量的名字和其所代表的对象之间的联系。那么为什么要把引用环境与函数组合起来呢?这主要是因为在支持嵌套作用域的语言中,有时不能简单直接地确定函数的引用环境。这样的语言一般具有这样的特性:

  • 函数是一阶值(First-class value),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。
  • 函数可以嵌套定义,即在一个函数内部可以定义另一个函数。
1&2:闭包是用来描述那个函数能够访问那个变量的这种能力的。而不是那个函数也不是那个变量。也就是一个函数能访问其词法范围之外的变量的能力。
3:永久保存在内存?只是将对象的生命延长到和使用它的函数一致。如果所有使用它的函数都被回收了,这个变量也就回收了。(好吧,在实践中,这属于钻牛角尖了,而且还要看JS引擎如何实现)

闭包在JS中的作用:
隐藏私有变量
将额外参数传递给运行时生成的函数 (因为你生成的函数会被别人调用,比如回调函数,毫无疑问函数的参数个数和次序已定,但你的函数确实需要更多信息)

需要注意的问题:
一个变量进入闭包后,会被复制到堆中,所有对该变量的使用都解析为引用这个堆中的变量。所以,使用这个变量的所有函数都会读取到最新的值,而不是你创建该函数时的值。

其实 专业一点的JS书中都有解释。
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn