翻译原文:http://blog.csdn.net/Ant_Yan/archive/2008/09/24/2972572.aspx
英文原文:http://jibbering.com/faq/faq_notes/closures.html
Closure介绍
Js闭包是一种封闭代码块(一般来说是一个函数),它包含了自由变量和绑定这些自由变量的环境,这些变量不是在这个代码块或者全局作用域定义的,而是在定义代码块的环境中定义。
闭包是Javascript中最强大的特性之一,但是在完全理解它之前这种强大的功能很难能发挥出来。创建闭包相对而言比较简单,有时甚至是意外的创建了闭包,而意外闭包的创建往往存在潜伏的危害,特别是当它存在于浏览器的公共环境中。 为了避免意外的引入有危害的闭包、而又要受益于闭包的强大功能,就有必要了解它的机制。首先要了解的是作用域链(Scope Chain)在Javascript如何定位变量的、而每个对象的property值又是如何解析的。
另一种对Js闭包的理解是:Javascript允许内部函数,即函数的声明定义是完全被包含在另外一个函数的函数体之内。这个内部(Inner)函数有权限访问自身和它外部(Outer)函数所有的local变量、parameter值(Js没有块级作用域)。当这个内部函数在包含它的Outer函数之外仍然可以访问的时候,一个闭包就形成了。这个时候所有Outer函数的local变量、parameter值包括Inner函数的声明也是处于可访问状态。这些变量和Inner函数声明是保持上一次Outer函数调用返回时候的值。
对象的属性名解析
ECMAScript(Javascript) 识别两种类型的对象,Native对象和Host对象,Native对象也有另外一种名称叫做Built-in(内置)对象。内置对象来源于Js语言本身,而Host对象是运行环境中产生的,比如document对象和DOM各节点对象等。
内置对象的属性名命名很宽松,每个命名的属性具有一个值,可能引用另外一个对象也可能是一个基本类型数据值,这里函数也属于对象,基本数据包括String、Number、Null or Undefined。这个Undefined或许有些奇怪因为具有Undefined值的属性其实有定义的(至少该命名的属性是存在),只不过具有的值是Undefined而已。当一个属性被赋值时,如果取属性的对象并没有定义这个属性,一个同名新的属性就会被定义并完成赋值,如果已经定义就执行re-set操作。
读取值的时候由于每个对象都有原型对象prototype属性,而这个prototype本身也是一个对象,对象就可以绑定属性值,当这个prototype对象上含有要读取的同名属性时,它的值就会被返回作为读取结果。那么既然prototype也是对象,对象又会具有它的prototype,这样就会形成一个原型链。当原型链中某个对象拥有一个为null的prototype属性时原型链就终结。默认情况下Object的构造函数具有一个null值的原型链。所以var ref= new Object() 将会创建一个对象,它具有的原型对象Object.prototype值为null。
function MyObject1(param1){
this.testNumber = param1;
}
function MyObject2(param2){
This.testString = param2;
}
MyObject2.prototype = new MyObject1(8);
var ref = new MyObject2(“String value”);
MyObject2的这个实例ref拥有一个原型链。链上第一个对象是一个MyObject1的实例对象,这个对象创建出来然后赋值或者说挂载在MyObject2的原型对象上。而MyObject1有自己的原型链,链上仅有一个对象就是默认的Object.prototype,由于它已经为null原型链的解析终结。当从ref从读取testString值时它直接从MyObject2中找到改属性并返回;当读取testNumber时需要遍历到原型链中的MyObject1实例上才能找到匹配的属性,当读取toString属性时就遍历到了Object基类的原型对象上才找到匹配属性。如果都无法匹配则返回undefined。
读取值的顺序是依次读取对象自身域、然后是原型对象域、然后原型域对象的原型域,递归下去直到为原型属性为null,遇到第一个匹配的属性值就返回结果。
执行上下文(Execution Context)
执行上下文(Execution context)是一个抽象的概念,用来定义Javascript脚本执行的环境。所有Js代码将在上下文中执行,全局Js将在一个叫做全局上下文的环境中执行,所有函数调用(包括构造对象)将会产生一个函数上下文,由eval调用执行的Js也会关联到一个特定的执行上下文中(虽然几乎部怎么使用)。
当一个Js函数被调用时就进入了它对应的函数上下文,当在这个上下文中另外一个函数被调用或者本身函数被二次调用时,一个新的函数上下文又会产生。这个函数上下文一直持续存在到函数调用结束。执行环境将会退出到第一个函数上下文中,于是这里就形成了一个执行上下文的栈式结构 (调用栈)。
当一个函数上下文被创建的时候,一系列事情按顺序发生。首先,在函数上下文中有一个叫做”Activation Object”的对象被创建出来,它属于另外一种创建机制因为它具有某些命名属性类似对象但又不具有prototype属性并且无法被Js代码直接引用。下一步,在函数上下文中创建arguments对象,一个类似数组的结构,可以按照整数下标顺序取出里面的元素。它具有length和callee属性。之前的Activation Object有一个同名为arguments的属性将作为arguments对象的引用。再下一步,这个函数上下文会被赋值或者说挂载到一个作用域。一个作用域包含了一连串对象。每一个函数对象也将包含内置作用域属性包含着一连串的对象,即函数关联的所有属性值,函数作用域链最终被挂载到函数上下文上,而Activation Object就作为这条作用域链的第一个元素。
再下一步将是变量初始化的过程,这个时候Activation Object充当一个变量载体的角色,称之为”Variable Object”,根据函数参数名命名相应的属性,如果参数与arguments数组元素正好数量对应则依次赋值,否则若arguments过长则多出来的值没有属性名,如果arguments元素不够则有些参数会赋值undefined。内部函数会以它声明的名字作为属性名存储到Variable Object上,最后一步初始化就是针对所有的local变量根据属性名赋初始化值为undefined,所有的local变量只有在真正调用时才会根据实际情况被赋值。从Activation Object和Variable Object其实是同一个对象的角度来理解,arguments也成了一个local变量。
终于函数上下文创建完毕,之后所有的变量访问都必须使用this关键字,即使没有显式也会被访问器自动加上this前缀。如果属性属于Object即要么有值要么为undefined则this引用这个对象实例,若为null则说明该对象不包含这个属性this引用全局对象。
全局上下文比较特殊,它不存在arguments属性所以也不需要定义Activation Object,它也不需要一个作用域对象,因为它的作用域里永远只有一个对象就是全局对象自身。它也不必执行变量初始化的过程,对于全局对象来说所有的inner函数其实就是最上层声明的普通函数而已。不过它同样会使用this关键字指向这个全局对象,它仅仅简单的被当作一个变量载体来使用,所以所有的顶层函数和全局变量其实都是这个全局对象的属性之一。
作用域链(Scope Chain)
函数调用时作用域链是通过挂载/添加执行上下文中Variable Object作为头节点来构造的,本身它也作为函数对象的一个属性。由于Javascript中函数也是对象,它们在扫描函数声明的变量初始化阶段就已经被创建,创建时候调用了Function的constructor,这里的Function是指Js的一个内置对象。通过这个构造器创建的函数对象都会有一个属性指向作用域链初始化包含唯一的全局对象。
当构造唯一的全局对象时,扫描所有的顶层函数并完成对全局对象的作用域链属性解析,所有顶层函数对象的作用域链也是在这个时候形成,并且都会带有全局对象的引用。
内部函数会造成函数对象的构造在另外一个函数上下文环境中,这就使得它的作用域链对象更加复杂。考虑一个例子况:当一个Outer函数被调用时,会创建一个新的函数上下文环境,这个上下文环境的作用链包括一个新的Activation Object和函数对象本身的作用域链(scope)属性。在变量初始化阶段一个内部函数对象被创建并带有自身的作用域链属性,这个内部函数对象的作用域链依次挂载了此函数的Activation Object和全局对象。到目前为止,这一系列动作都是在源代码和Js脚本语言机制下自动完成的,执行上下文的作用域链属性定义了所有被创建的函数对象,而函数对象的作用域链属性定义了它们被调用时启动新的上下文环境的作用域。ECMAScript提供了一个”with”语法来意图修改作用域链。
with 语句接受一个表达式参数,如果表达式是一个对象,它将会被挂载到当前执行上下文环境的作用域链上来,并且位置位于Activation Object之前。接下来with执行一个块语句,执行完毕会还原之前的作用域链。函数声明无法在with语句块中发挥作用,因为它仅仅创建了一个函数对象不存在执行环境,但函数表达式可以在with语句块中被执行。
标识解析(Identifier Resolution)
标识的解析正好跟作用域链相反,ECMA规范把this当作一个关键字而不是标识是不太合理的,因为每次都是在执行上下文中都是依赖this来解析标识而不是通过作用域链。标识的解析从作用域链的第一个元素开始,先检查第一个作用域中是否存在这个标识,因为作用域链上的都是一个个对象,所以这种检查也会覆盖对象的原型链。如果第一个作用域对象上无法找到标识同名的属性名就检查第二个,依次遍历下去直到直到该属性或者作用域链条终结。对已经找到解析的标识进行操作会跟操作对象的属性遵循一样的流程(读/写)。
函数被调用时关联上的执行上下文将会把Activation Object放在作用域链的第一个位置,标识解析也会首先检查Activation Object对象的属性,看是否能在函数参数、内部函数名、local变量中找到对应的名字。
Closures (闭包)
垃圾回收机制 (Garbage Collection)
ECMAScript使用自动垃圾回收机制,规范中并没有定义垃圾回收的细节,而是把具体实现留给了各浏览器厂商,比如有一些实现赋予垃圾回收器非常低的优先级(可能导致内存泄漏)。一般而言默认的原则是一个无法被引用了的对象,即所有对它的引用都变得不可访问的时候,它就变成了一个可回收的对象并且在将来的某个时间会被销毁,它占用的一切资源也可以释放。
垃圾回收典型的触发时机是当一个执行上下文环境退出时,作用域链结构、Activation Object和所有在执行上下文环境中创建的对象,包括函数对象,都将变得不可访问。所以,对垃圾收集器来说它们都可回收。
形成闭包
闭包是这样形成的:在Outer函数调用时产生执行上下文环境,Outer函数中存在一个Inner函数,内部函数对象的引用作为返回值退出了Outer函数,被赋给了另外一个对象的某个属性或者某全局变量。例:
- function exampleClosureForm(arg1, arg2){
- var localVar = 8;
- function exampleReturned(innerArg){
- return (arg1+arg2)/(innerArg+localVar);
- }
- return exampleReturned;
- }
- var globalVar = exampleClosuresForm(2,4);
现在对于exampleClosureForm的这次调用创建的执行上下文来说, 内部创建的函数对象无法被垃圾收集器回收,因为它仍然处于可访问状态,可以通过globalVar(5)来触发它。然而现在有更复杂的事情发生了,就是globalVar引用的函数对象自身带有一个作用域链属性,包含着一个Activation Object元素,这个Activation Obje
分享到:
相关推荐
closure-compiler-v20170521.jar,以及一个.chm使用说明:‘Getting Started with the Closure Compiler Application’,‘Advanced Compilation and Externs’,‘Understanding the Restrictions Imposed by the ...
这个存储库包含JavaScript Closure演示。 在我们开始讨论闭包之前,重要的是我们理解以下术语,因为它们将构成我们对闭包的整个理解的基线: 1. 词法范围 词法是指语言或文本。 范围是指与文本相关的属性。 词法...
Closure Library是Google开发的一个强大的、模块化的JavaScript库,旨在提供高效、可维护的代码解决方案。这个库被设计为可跨浏览器、跨平台使用,确保在各种JavaScript环境中的一致性。Closure Library的核心理念是...
闭包(Closure)是一个可以包含自由变量的代码块,这个自由变量并不是在这个代码块或任何全局上下文中定义的,而是在定义代码块的环境中定义。简单来说,闭包就是在一个函数内部定义另一个函数,内层函数可以引用...
Closure编译器是Google开发的一款强大的JavaScript优化工具,它的纯JavaScript版本为开发者提供了一种高效、先进的代码构建方案。此工具旨在提升JavaScript代码的质量、性能和可维护性,通过压缩、优化以及处理代码...
JavaScript Closure 的 Javadoc文档 由于互联网是没有于是自己制作成了.chm格式的
If you're ready to use Closure to build rich web applications with JavaScript, this hands-on guide has precisely what you need to learn this suite of tools in depth. Closure makes it easy for ...
Closure Compiler是Google开发的一款强大的JavaScript代码优化工具,其主要功能是对JavaScript代码进行压缩和混淆,以提高代码的运行效率和安全性。"closure-compiler-v20171112.jar"是该编译器的一个特定版本,发布...
闭包(Closure)是JavaScript中的一个核心概念,它是指有权访问独立变量的函数,即使是在该函数的外部。闭包允许函数访问并操作函数外部的变量,这是通过在函数定义的时候捕获自由变量实现的,自由变量是在函数外部...
Google Closure 是一个强大的JavaScript开发工具集,由Google开源并维护。这个框架包含了多个部分,旨在帮助开发者编写高质量、高性能的JavaScript代码。Closure的核心组件包括: 1. **Closure Library**:这是一个...
Closure Library 是一个广泛使用的 JavaScript 库,尤其在构建大型、高性能的 Web 应用程序时非常有用。这个库提供了大量的实用工具函数、面向对象的支持以及高级组件,如 AJAX、事件处理、动画效果等,有助于开发者...
Closure Compiler是一款由Google开发的高级JavaScript编译器,它能够对源代码进行静态分析和优化,包括删除未使用的变量、函数和类,以及进行类型检查,将变量名混淆(minification)等,从而显著减小代码体积并提高...
Closure Linter是一款强大的静态代码分析工具,主要用于检查JavaScript代码的质量,确保代码符合Google的 Closure编程风格指南。这个压缩包提供了一个完整的Closure Linter安装包,包括必要的依赖和安装说明,以便...
在Java中,你可以使用Lambda表达式或匿名内部类来创建闭包,而在JavaScript中,所有的函数都是闭包。在C#中,可以使用`Action`、`Func`等委托类型或`lambda`表达式来创建闭包。 总的来说,闭包在编程中扮演着至关...
**Closure Loader - 前端开发的利器** Closure Loader是一个专为前端开发者设计的开源工具,主要...如果你的项目中使用了Closure Library,或者你追求高性能、高可靠性的JavaScript应用,Closure Loader绝对值得尝试。
Closure Compiler是一款强大的JavaScript代码压缩器,它能够通过删除冗余代码、优化变量名和函数名,以及处理死代码,来减小JavaScript文件的大小,提高网页加载速度,并有助于提升代码性能。而Maven Antrun插件则...
闭包编译器是一个让javascript下载和运行更快的工具。它是一个真正的javascript编译器。它不是从源语言编译成机器代码,而是从javascript编译成更好的javascript。它解析你的javascript,分析它,删除死代码,重写并...
谷歌闭包编译器(Google Closure Compiler)是一款强大的JavaScript代码优化工具,由Google开发并维护。它通过对JavaScript代码进行语法分析和压缩,帮助开发者减小代码体积,提高网页加载速度,同时也能消除潜在的...