您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 项目/工程管理 > JavaScript闭包
note1|note2Javascript闭包翻译:为之漫笔链接:简介基于对象的属性名解析o值的指定o值的读取标识符解析、执行环境和作用域链o执行环境o作用域链与[[scope]]o标识符解析闭包o自动垃圾收集o构成闭包通过闭包可以做什么?o例1:为函数引用设置延时o例2:通过对象实例方法关联函数o例3:包装相关的功能o其他例子意外的闭包InternetExplorer的内在泄漏问题简介Closure所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。闭包是ECMAScript(JavaScript)最强大的特性之一,但用好闭包的前提是必须理解闭包。闭包的创建相对容易,人们甚至会在不经意间创建闭包,但这些无意创建的闭包却存在潜在的危害,尤其是在比较常见的浏览器环境下。如果想要扬长避短地使用闭包这一特性,则必须了解它们的工作机制。而闭包工作机制的实现很大程度上有赖于标识符(或者说对象属性)解析过程中作用域的角色。关于闭包,最简单的描述就是ECMAScript允许使用内部函数--即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。遗憾的是,要适当地理解闭包就必须理解闭包背后运行的机制,以及许多相关的技术细节。虽然本文的前半部分并没有涉及ECMA262规范指定的某些算法,但仍然有许多无法回避或简化的内容。对于个别熟悉对象属性名解析的人来说,可以跳过相关的内容,但是除非你对闭包也非常熟悉,否则最好是不要跳过下面几节。对象属性名解析ECMAScript认可两类对象:原生(Native)对象和宿主(Host)对象,其中宿主对象包含一个被称为内置对象的原生对象的子类(ECMA2623rdEdSection4.3)。原生对象属于语言,而宿主对象由环境提供,比如说可能是文档对象、DOM等类似的对象。原生对象具有松散和动态的命名属性(对于某些实现的内置对象子类别而言,动态性是受限的--但这不是太大的问题)。对象的命名属性用于保存值,该值可以是指向另一个对象(Objects)的引用(在这个意义上说,函数也是对象),也可以是一些基本的数据类型,比如:String、Number、Boolean、Null或Undefined。其中比较特殊的是Undefined类型,因为可以给对象的属性指定一个Undefined类型的值,而不会删除对象的相应属性。而且,该属性只是保存着undefined值。下面简要介绍一下如何设置和读取对象的属性值,并最大程度地体现相应的内部细节。值的赋予对象的命名属性可以通过为该命名属性赋值来创建,或重新赋值。即,对于:varobjectRef=newObject();//创建一个普通的javascript对象。可以通过下面语句来创建名为“testNumber”的属性:objectRef.testNumber=5;/*-或-*/objectRef[testNumber]=5;在赋值之前,对象中没有“testNumber”属性,但在赋值后,则创建一个属性。之后的任何赋值语句都不需要再创建这个属性,而只会重新设置它的值:objectRef.testNumber=8;/*-或-*/objectRef[testNumber]=8;稍后我们会介绍,Javascript对象都有原型(prototypes)属性,而这些原型本身也是对象,因而也可以带有命名的属性。但是,原型对象命名属性的作用并不体现在赋值阶段。同样,在将值赋给其命名属性时,如果对象没有该属性则会创建该命名属性,否则会重设该属性的值。值的读取当读取对象的属性值时,原型对象的作用便体现出来。如果对象的原型中包含属性访问器(propertyaccessor)所使用的属性名,那么该属性的值就会返回:/*为命名属性赋值。如果在赋值前对象没有相应的属性,那么赋值后就会得到一个:*/objectRef.testNumber=8;/*从属性中读取值*/varval=objectRef.testNumber;/*现在,-val-中保存着刚赋给对象命名属性的值8*/而且,由于所有对象都有原型,而原型本身也是对象,所以原型也可能有原型,这样就构成了所谓的原型链。原型链终止于链中原型为null的对象。Object构造函数的默认原型就有一个null原型,因此:varobjectRef=newObject();//创建一个普通的JavaScript对象。创建了一个原型为Object.prototype的对象,而该原型自身则拥有一个值为null的原型。也就是说,objectRef的原型链中只包含一个对象--Object.prototype。但对于下面的代码而言:/*创建-MyObject1-类型对象的函数*/functionMyObject1(formalParameter){/*给创建的对象添加一个名为-testNumber-的属性并将传递给构造函数的第一个参数指定为该属性的值:*/this.testNumber=formalParameter;}/*创建-MyObject2-类型对象的函数*/functionMyObject2(formalParameter){/*给创建的对象添加一个名为-testString-的属性并将传递给构造函数的第一个参数指定为该属性的值:*/this.testString=formalParameter;}/*接下来的操作用MyObject1类的实例替换了所有与MyObject2类的实例相关联的原型。而且,为MyObject1构造函数传递了参数-8-,因而其-testNumber-属性被赋予该值:*/MyObject2.prototype=newMyObject1(8);/*最后,将一个字符串作为构造函数的第一个参数,创建一个-MyObject2-的实例,并将指向该对象的引用赋给变量-objectRef-:*/varobjectRef=newMyObject2(String_Value);被变量objectRef所引用的MyObject2的实例拥有一个原型链。该链中的第一个对象是在创建后被指定给MyObject2构造函数的prototype属性的MyObject1的一个实例。MyObject1的实例也有一个原型,即与Object.prototype所引用的对象对应的默认的Object对象的原型。最后,Object.prototype有一个值为null的原型,因此这条原型链到此结束。当某个属性访问器尝试读取由objectRef所引用的对象的属性值时,整个原型链都会被搜索。在下面这种简单的情况下:varval=objectRef.testString;因为objectRef所引用的MyObject2的实例有一个名为“testString”的属性,因此被设置为“String_Value”的该属性的值被赋给了变量val。但是:varval=objectRef.testNumber;则不能从MyObject2实例自身中读取到相应的命名属性值,因为该实例没有这个属性。然而,变量val的值仍然被设置为8,而不是未定义--这是因为在该实例中查找相应的命名属性失败后,解释程序会继续检查其原型对象。而该实例的原型对象是MyObject1的实例,这个实例有一个名为“testNumber”的属性并且值为8,所以这个属性访问器最后会取得值8。而且,虽然MyObject1和MyObject2都没有定义toString方法,但是当属性访问器通过objectRef读取toString属性的值时:varval=objectRef.toString;变量val也会被赋予一个函数的引用。这个函数就是在Object.prototype的toString属性中所保存的函数。之所以会返回这个函数,是因为发生了搜索objectRef原型链的过程。当在作为对象的objectRef中发现没有“toString”属性存在时,会搜索其原型对象,而当原型对象中不存在该属性时,则会继续搜索原型的原型。而原型链中最终的原型是Object.prototype,这个对象确实有一个toString方法,因此该方法的引用被返回。最后:varval=objectRef.madeUpProperty;返回undefined,因为在搜索原型链的过程中,直至Object.prototype的原型--null,都没有找到任何对象有名为“madeUpPeoperty”的属性,因此最终返回undefined。不论是在对象或对象的原型中,读取命名属性值的时候只返回首先找到的属性值。而当为对象的命名属性赋值时,如果对象自身不存在该属性则创建相应的属性。这意味着,如果执行像objectRef.testNumber=3这样一条赋值语句,那么这个MyObject2的实例自身也会创建一个名为“testNumber”的属性,而之后任何读取该命名属性的尝试都将获得相同的新值。这时候,属性访问器不会再进一步搜索原型链,但MyObject1实例值为8的“testNumber”属性并没有被修改。给objectRef对象的赋值只是遮挡了其原型链中相应的属性。注意:ECMAScript为Object类型定义了一个内部[[prototype]]属性。这个属性不能通过脚本直接访问,但在属性访问器解析过程中,则需要用到这个内部[[prototype]]属性所引用的对象链--即原型链。可以通过一个公共的prototype属性,来对与内部的[[prototype]]属性对应的原型对象进行赋值或定义。这两者之间的关系在ECMA262(3rdedition)中有详细描述,但超出了本文要讨论的范畴。标识符解析、执行环境和作用域链执行环境执行环境是ECMAScript规范(ECMA262第3版)用于定义ECMAScript实现必要行为的一个抽象的概念。对如何实现执行环境,规范没有作规定。但由于执行环境中包含引用规范所定义结构的相关属性,因此执行环境中应该保有(甚至实现)带有属性的对象--即使属性不是公共属性。所有JavaScript代码都是在一个执行环境中被执行的。全局代码(作为内置的JS文件执行的代码,或者HTML页面加载的代码)是在我将称之为“全局执行环境”的执行环境中执行的,而对函数的每次调用(有可能是作为构造函数)同样有关联的执行环境。通过eval函数执行的代码也有截然不同的执行环境,但因为JavaScript程序员在正常情况下一般不会使用eval,所以这里不作讨论。有关执行环境的详细说明请参阅ECMA262(第3版)第10.2节。当调用一个JavaScript函数时,该函数就会进入相应的执行环境。如果又调用了另外一个函数(或者递归地调用同一个函数),则又会创建一个新的执行环境,并且在函数调用期间执行过程都处于该环境中。当调用的函数返回后,执行过程会返回原始执行环境。因而,运行中的JavaScript代码就构成了一个执行环境栈。在创建执行环境的过程中,会按照定义的先后顺序完成一系列操作。首先,在一个函数的执行环境中,会创建一个“活动”对象。活动对象是规范中规定的另外一种机制。之所以称之为对象,是因为它拥有可访问的命名属性,但是它又不像正常对象那样具有原型(至少没有预定义的原型),而且不能通过JavaScript代码直接引用活动对象。为函数调用创建执行环境的下一步是创建一个argument
本文标题:JavaScript闭包
链接地址:https://www.777doc.com/doc-5455058 .html