当前位置:编程学习 > JAVA >>

Javascript Closures

介绍:
一个闭包是一个表达式(通常是函数),能在这个表达式的封闭环境中自由使用绑定在这个环境中的变量。

闭包是ECMAScript中最强大的功能之一。如果不理解闭包,就不能被正确的利用它。闭包很容易创建,甚至是在无意中。而它们的创建有一定的潜在威胁,尤其在一些常见的web浏览器环境中。为了避免意外遭遇和能利用闭包提供的优点,我们需要理解它的操作原理。这很大程度上依赖于作用域链在标识符解析中的角色,和对象上属性命名的解决方案。

简单的闭包解释:ECMAScript允许在其他函数体内的内部函数,函数定义和函数表达式,访问所有的本地变量,参数和申明这个内部函数的外部函数。当一个这种内部函数访问包含它的外部函数时就构成了闭包,以便外部函数返回该内部函数后执行。这时它的引用,仍然能够访问本地变量,参数和申明该内部函数的外部函数。外部函数返回以后,这些本地变量,参数和函数的初始值仍然有效,并且可以和内部函数交互。

不幸的是,要正确的理解闭包,需要理解它们后面的操作原理,涉及到许多的技术细节。在下文中粗略的介绍ECMA 262中一些不能跳过必须掌握的特有算法。有些人可能已经熟悉了对象命名,可以跳过下面章节。但除非你已经熟悉了闭包,能跳过所有章节,可以直接去写你的闭包代码了,否则,还是建议你仔细阅读下面的内容。

 


对象的属性命名解放方案
ECMAScript只承认两类对象,本地对象和宿主对象("Native Object"&"Host Object")。其中Native Objects的子类中有一类:内置对象("Built-in Object", ECMA 262 3rd Ed Section 4.3)。Native objects属于语言本身。Host objects是由环境提供,例如document对象,DOM节点之类的。
Native objects是松散的,而且动态的包含命名属性的(可能部分的实现中built-in object子类并非动态的,但这无关紧要)。
一个对象的命名属性是name-value表。这个值可能引用其它对象(函数也是对象),或者原始值:String, Number, Boolean, Null 或 Undefined。Undefined是一个奇怪的原始类型。它可以赋值给一个对象的属性,但并不是移除了这个属性,这个属性仅仅是持有了一个undefined的值。
赋值
对象的命名属性可以通过赋值给一个属性名创建,或者给一个已经存在的属性名称赋值。所以:
var objectRef=new Object();//创建一个通用的JS对象
一个属性名为"testNumber"可以如此创建:
objectRef.testNumber = 5;
/* - or:- */
objectRef["testNumber"] = 5;
对象在赋值之前没有"testNumber"属性,将会创建一个。随后任何赋值都不会创建属性,只是重新设置它们的值。
objectRef.testNumber = 8;
/* - or:- */
objectRef["testNumber"] = 8;
Javascript 对象都有原型,原型本身又可以作为对象。简单的说,原型可能也有命名属性,但这和赋值无关。如果一个值被赋予对象的命名属性,而实际对象在对应的命名属性中没有该属性,那么创建这个属性并将这个值赋给它。如果有属性,那么它的值被重置。
读取值
当从对象的属性中读取值时,原型开始参与了。如果一个对象在属性中含有,访问者使用的属性名,那么这个属性的值被返回:
/* Assign a value to a named property. If the object does not have a
   property with the corresponding name prior to the assignment it
   will have one after it:-
*/
objectRef.testNumber = 8;


/* Read the value back from the property:- */


var val = objectRef.testNumber;
/* and  - val - now holds the value 8 that was just assigned to the
   named property of the object. */
但是所有对象都可能有原型,而原型也是对象,反过来,原型也有原型。由此构成了原型链。原型链当遇到原型是null时才会停止。object构造器的默认原型是一个null原型,所以:
var objectRef = new Object(); //create a generic javascript object.
用Object.prototype原型创建了一个对象,Object.prototype本身有一个指向null的原型。所以objectRef的原型链,仅包含一个对象:Object.prototype.但是:
/* A "constructor" function for creating objects of a -
   MyObject1 - type.
*/
function MyObject1(formalParameter){
    /* Give the constructed object a property called - testNumber - and
       assign it the value passed to the constructor as its first
       argument:-
    */
    this.testNumber = formalParameter;
}


/* A "constructor" function for creating objects of a -
   MyObject2 - type:-
*/
function MyObject2(formalParameter){
   /* Give the constructed object a property called - testString -
      and assign it the value passed to the constructor as its first
      argument:-
    */
    this.testString = formalParameter;
}


/* The next operation replaces the default prototype associated with
   all MyObject2 instances with an instance of MyObject1, passing the
   argument - 8 - to the MyObject1 constructor so that its -
   testNumber - property will be set to that value:-
*/
MyObject2.prototype = new MyObject1( 8 );


/* Finally, create an instance of - MyObject2 - and assign a reference
   to that object to the variable - objectRef - passing a string as the
   first argument for the constructor:-
*/


var objectRef = new MyObject2( "String_Value" );
objectRef变量引用MyObject2的实例,有一个原型链。在原型链中的第一个对象是MyObject1的实例(创建并且赋给了MyObject2构造器的prototype属性)。MyObject1的实例也有原型,这个对象通过实现方法赋值给MyObject1的原型属性。这个对象有一个原型,默认Object原型,引用Object.prototype。Object.prototype有一个引用null的原型,此刻原型链就结束了。
当一个属性访问者,尝试从一个对象的引用变量objectRef读取一个属性名,这条原型链都要进入处理过程。简单的场景:
var val = objectRef.testString;
objectRef引用MyObject2的实例,有一个名为"testString"的属性,它的值是"String_Value",所以这个值也就被赋值给了变量val,但是:
var val = objectRef.testNumber;
在MyObject2的实例本身不能读取到这个属性。但变量val却被赋值为8了,而不是undefined。虽然在当前对象自身的命名属性中找不到,解析器会轮询对象的原型。它的原型是MyObject1的实例,而这时它创建了一个属性值为8的"testNumber"的属性,所以属性访问者得出的值是8。MyObject1和MyObject2都没定义toString方法,但如果一个属性访问者,尝试从objectRef中读取toString属性的值。
var val = objectRef.toString;
这个val变量被赋值成了一个函数。这个函数是Object.prototype的toString属性(作用于对象)。所以当原型被发现缺少某个属性时,将轮次检查它的原型。它的原型Object.prototype,确实有一个toString方法,所以这个函数对象的引用被返回了。
最后:
var val = objectRef.madeUpProperty;

返回undefined,因为原型链的工作过程没有找到任何对象有一个名为"madeUpPeoperty"的属性,它最后获取到原型Object.prototype的属性null,而处理结果返回值undefined.读取属性名,返回在对象或它的原型链上找到的第一个值。给一个对象的属性赋值,如果它本身不存在这个属性,将在对象本身的属性上创建一个。

这意味着如果一个值赋值给objectRef.testNumber=3,一个"testNumber"属性将在MyObject2本身的实例上创建,而任何后续的读取该值的尝试,都将检索到这个值。属性访问者不在需要检查原型链了,但MyObject1的实例中的"testNumber"的值仍然是8,没有被改变。赋值给objectRef的对象,遮掩了它原型链中的相关属性。

 


注意:ECMAScript定义了一个内部Object类型的内部[[prototype]]属性。这个属性是不能用script脚本直接访问的,用于解决属性访问者的对象链涉及的内部[[prototype]]属性:对象的原型链。还存在一个公用的prototype属性,允许赋值,定义和操作内部的[[prototype]]属性。这两者之间的详细区别在ECMA 262(3rd edition)中,我们将在scope章节中讨论。

 


标识符解析,执行上下文和作用范围链
执行上下文:
执行上下文是ECMSScript(ECMA 262 3rd edition)白皮书中使用的一个抽象概念,定义ECMAScript必须实现的行为。白皮书中没有任何关于执行上下文应该如何实现的说明,但白皮书定义了执行上下文相关的属性结构,所以它们可能被认为(甚至实现)是对象的属性,虽然不是公共属性。
所有JS代码在一个执行上下文中执行。全局代

补充:web前端 , JavaScript ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,