`
lyunabc
  • 浏览: 531455 次
  • 性别: Icon_minigender_2
社区版块
存档分类
最新评论

从零开始学javascript之三-----闭包、函数与作用域

 
阅读更多

学习JavaScript也有快半年的时间了,虽然期间并不是一直抱着js啃,但是也花了相当多时间,学到了不少东西,将其诉诸于文字,是希望能够将别人的知识变成自己的知识,若是对大家还有点帮助,那就再好不过了。


从闭包说起

闭包是js中很重要的一个东西,因为它是函数产生的,而函数可以说是js中最核心的。要是没有听过闭包,赶紧翻开书看一遍概念。

闭包的定义可以说很简单,如果在A函数内定义了一个函数B,那么只要在函数A外部引用了函数B,这就形成了一个闭包。A执行完毕之后,因为引用的存在,B不会被注销,而A的活动对象也不会被注销。引用的形式多样,常见的是返回值的形式,比如直接返回,或者是返回了一个数组,其中一个数据项是函数,或者返回一个对象,其中一个键/值对包含函数,而比较特殊的一个是(funciton(){})(),这也是一个闭包。看明白了吗,如今很多库都采用这种形式,例如jQuery和Mootools,其过程是(function(){})外面的小括号将匿名函数返回,而(function(){})()第二个小括号将返回的函数执行。我以前的理解仅仅是(function(){})()创建了一个匿名函数,然后执行这个匿名函数,在看了《JavaScript DOM 高级程序设计》之后才知道它有闭包的作用。

这里再提一下匿名函数。函数的创建有两种方式,一种就是function a(){},另一种就是var a = function(){}。后面一种就是采用匿名函数来创建的,而a就是指向函数的指针。在chrome中无法创建一个没有指针的匿名函数,例如function(){…};这样会报错。因为没有指针就无法引用函数,无法调用的函数就没有意义,而前面(function(){…})()函数创建之后立即执行,是有意义的。

闭包的由来

函数是有作用域的,一个函数要能够执行,它里面的变量都必须要有完整的定义,所以函数的执行依赖于其作用域。闭包也是这样,它返回的是一个函数,同时也返回了函数的作用域。函数的生命周期是从声明到执行这段时间,函数被调用之后即被注销,但是里面那些被引用的变量是不会被注销的。而闭包是不会被注销的,它会整个存在于内存中,所以使用闭包的时候要清楚自己在做什么,不然可能会引起内存溢出。但是这里有一个疑问就是,一般来说闭包存在于全局对象中,如果一个闭包存在于一个函数中,当函数被注销时,不知道闭包会是怎么样的情况,暂时没找到方法去验证。

上面一段是以前对闭包的理解,不过看完周爱民大师的《Javascript语言精髓》之后,对闭包有了新的认识,两段对比,可以发现以前理解的一些误差。函数在预编译的时候会产生一个调用对象,这个调用对象包含了内部变量表(var声明的)、函数表(function声明的)和除此之外的代码(source)。而函数在被调用时会产生一个函数实例,每个实例会对应一个闭包,闭包中的数据是从调用对象中复制过来的。一般情况下,函数在执行完毕之后,函数实例和闭包都要被销毁。当再次执行的时候。但是函数被外部引用的时候,函数实例和闭包无法被销毁。实际上就是每个函数在运行时候都会产生闭包,只是有的闭包被销毁了,那些被引用的闭包保留了下来。而这些闭包保留的是函数实例的数据,也就是函数运行时的数据,当然这些数据能够在以后被再次访问和修改。

js里面作用域是比较奇特的,下面讲一下函数的作用域。

函数的作用域

js没有块级作用域,只有函数才有作用域。要了解函数的作用域,就必须清楚几个概念:执行环境,活动对象(不同书的叫法不太一样,有的是活动对象,有的是变量对象,有的是调用对象,这里统一叫活动对象),作用域链。

JavaScript中,函数有两个阶段:预编译和执行。在预编译期,函数的各个变量会被索引,建立一个符号表(包含字符变量、直接量、函数和类),根据符号表生成一个语法树,这里涉及到js的解析机制,可以看看我的这篇文章《JavaScript学习心得——解析机制》。函数在执行的时候就根据语法树来执行。js是词法作用域,js函数的作用域在预编译期就已经确定,存储在符号表中,跟执行期的关系不大,是一个静态作用域。

每个函数在执行期,都会创建一个执行环境,并在这个环境中创建一个活动对象,在这个对象内存储着当前作用域中所有变量、参数、嵌套函数、外部引用。这个时候变量对象中的属性和符号表中的同名属性进行一一映射,就完成了这个变量对象的赋值,执行环境的创建基本完成。

作用域链是保证对执行环境中所有变量的有序访问。在定义函数时,会将函数的作用域链(scope chain)设置为函数所在的环境的活动对象。函数执行的时候,会将函数的执行环境推入环境栈,并进入此执行环境。在执行环境中定义了作用域链和活动对象,作用域链指向外部环境的活动对象,这是一个链表结构。接着,执行环境会创建一个活动对象,并将活动对象推入作用域链的顶端。所以变量的查找是从当前函数的活动对象开始,逐步向外层环境的顺序,直到最外层的全局对象(Global)。

下面这幅图可以简单表示执行环境,语法树,活动对象之间的关系:

(此图非原创,查看原文

作用域链的机制和原型链的机制相似,原型链中实例中的__proto__属性会引用prototype对象,而prototype的__proto__属性会引用下一个prototype对象。作用域链中scope属性引用外层环境的作用域,外层环境的scope属性引用更外层的作用域。都是一种链表结构。

作用域链的前端是当前环境的活动对象,活动对象初始时包含了arguments和一些其他的命名参数,而this在函数执行时才被推入活动对象,而arguments的值也在执行时确定。

关于函数和作用域的问题暂时写到这里。第一次写东西,有些问题可能阐述得不够清楚,还请多多指正。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics