浅谈JavaScript的闭包

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈JavaScript的闭包相关的知识,希望对你有一定的参考价值。

  javascript是一门动态的,弱类型的脚本语言。和大多数编程语言一样,JavaScript也采用词法作用域。也即是说,函数的执行依赖于变量作用域。这个作用域是在函数定义时决定的,而非函数调用。函数对象可以通过作用域链互相关联起来,函数体可访问的变量都可以保存在函数作用域内部。闭包,一个形象的解释就是,函数变量隐匿于作用域链中,因此看起来是函数将变量包裹了起来。

  要理解闭包,就需要先分清以下几个概念:1.作用域(scope);2.作用域链(scope chain);3.运行期上下文,或者称为执行环境(execution context);4.活动对象(activation object)以及5.全局对象(Global Object),以下分别解释这几个概念。

  1.作用域。简而言之,变量的作用域就是指变量的可访问范围,或者变量的生命周期。JavaScript中的变量可分为全局变量和局部变量。其中全局变量包括:1.顶级函数(即未被嵌套到其他函数中的函数),顶级函数外(也包括顶级代码段外,顶级循环体外)定义的变量;2.未被var声明的初始化变量,例如a=6;3.window对象的属性;而局部变量就是在函数体内部定义的变量。

  这里需要区分的一个概念是:块级作用域和函数作用域。有Java或C#等高级程序设计开发经历的童鞋们知道,在这种类型的语言中,变量的作用域被严格限制在了代码块中,例如循环体,if语句等。

for(int i=0;i<10;i++)
{

}
console.write(i);//会报错,因为变量i的作用范围被限制在了for循环体中

  而JavaScript不同之处在于,它的变量作用域是函数作用域。即变量在声明它们的函数体以及这个函数嵌套的任意函数体内都是有定义的。举个例子,

  

function test() {
            if(true)
            {
                var j=0;
            }
            for(var k=0;k<10;k++)
            {

            }
            console.log(k);
            console.log(n);
            console.log(j);
            var n=3;

        }
        test();

  其中k=10,n=undefined,j=0。

  2.作用域链和全局对象

  作用域链,简而言之就是函数执行的时候可访问到的所有对象,函数,变量所组成的一个序列。函数执行的时候,去解析变量,就会按照这个排列的先后顺序去解析。如果在这个链上未解析到这个变量,那么程序将会抛出一个异常。

  以下分三种情况分析作用域链:1.在JavaScript的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链由一个全局对象组成;2.在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。

  当一个函数被创建的时候,其作用域链会填入全局对象,全局对象中含有所有的全局变量。例如以下函数:

  

function a(x,y)
{
    var b=x+y;
    return b;  
}

sum函数被创建的时候,其函数作用域如下:

技术分享

  3.执行环境和活动对象

  当函数a执行的时候,会生成一个执行环境,或者叫运行期上下文(execution context)。活动对象是指,值按照它们出现在函数中的顺序被复制到运行期上下文中,组成的新对象。其中包含了函数的局部变量,命名参数,参数集合以及this。它被推入作用域链的最前端,作为第一个对象,当运行期上下文被销毁时,活动对象也随之销毁。如果此函数被嵌套在其他函数中,那么运行期上下文的第二个对象就是包含环境,第三个对象就是包含环境的包含环境,一次类推,最后是全局对象。而运行期上下文就是指这所有的对象集。

  技术分享

 

  当函数执行的时候,解析变量的顺序就是按照这个对象的顺序依次解析,如果在对象中遇到对应的变量,那么解析停止。这些对象排列的数据结构类似链条,因此形象的称为作用域链。

  当我们理解了这些定义以后,回过头来看闭包,就容易理解多了。

   首先来看一段代码:

function checkScope()
{
  var scope=‘local scope‘;
  function f()
   {
      return scope;
   }    
  return f;
}    
var result=checkScope()();
console.log(result);

  这里顺便提一下,我们平时写代码的时候一定要养成良好的习惯,特别是要注意缩进的问题,好的缩进不仅可以使程序更加美观,也更加易读,不易出错。

  上面的代码输出结果是local scope。有很多人会感到疑惑,他们认为既然scope是函数checkScope内部定义的一个局部变量,那么当函数返回的时候就应当会销毁,从而报错。

  其实这也很容易理解,让我们回顾一下上面提到的概念,我们将作用域链描述为一个对象列表,这个列表中包含了所有函数可以访问的数据。每次调用JavaScript函数的时候,都会为之创建一个新的对象来保存局部变量,并把这个对象添加至作用域链中。当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数(嵌套函数的作用域链中也包含该对象,也即是说,嵌套函数可以访问该对象中的数据),也没有其他应用指向这个绑定对象,它就会当做垃圾被回收。但是如果这个函数定义了嵌套函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套函数,他就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收。

  checkScope函数返回了它的嵌套函数f,f的作用域链中包含了checkScope函数的scope变量,当checkScope返回的时候,f不会当做垃圾回收,因此也就保存了scope这个局部变量。

  因此闭包的最大作用就是可以读取函数内的变量,以及让这些变量的值始终保存在内存中。

  关于闭包的概念大概就介绍这些,给大家推荐一本JavaScript的学习书籍,JavaScript权威指南,里面的内容很丰富很全面,同时也富有学术气息~~

  如果你喜欢这篇文章的话,就麻烦推荐一下吧~~

以上是关于浅谈JavaScript的闭包的主要内容,如果未能解决你的问题,请参考以下文章

浅谈JavaScript--闭包

浅谈JavaScript的闭包

浅谈JavaScript闭包this指针作用域

浅谈JavaScript闭包

浅谈JavaScript匿名函数与闭包

javaScript--浅谈闭包