分享

Javascript Closures (1)

 Tom5 2009-05-18

Javascript Closures (1)

02月 22nd, 2009 — Dreamer

昨天又翻了下前段时间WD内部培训的幻灯片,发现了kejun推荐的一篇好文:Javascript Closures, 看了之后受益匪浅。这篇文章不是简单地告诉你Javascipt 中的 closure 的好处和用法,而是从ECMAScript中的定义开始,帮助你从底层的机制和原理深入了解closure。了解了底层的机制之后你就不会单纯停留在使用 的层面上,还会明白所有变量的初始化过程、作用域等,才算是真正掌握了closure。由于文章太长了,我就不翻译了,仅在此做一些笔记,仅供参考。另 外,文中很多东西需要仔细琢磨一下,一下子总结出来对我来说比较吃力,所以决定分成几个部分来说,本文是第一部分。

原文:Javascript Closures

1.简介

闭包(closure) 是 JS 最强大的特性之一,简单地说,闭包就是内部方法,即定义在方法内的方法,它们可以访问外部方法内的变量和参数, 即使外部方法的执行已经终止 。例如:

1
2
3
4
5
6
function example(arg1){
var localVar = 2;
return function inner(arg2){
return arg1+localVar + arg2;
}
}

要了解闭包的内部机制不是一件简单的事情,有许多准备工作要做。我们先来看一下有关对象的一些东西。

2.对象和对象属性

在 JS 中一切都是对象,包括 function 。对象可能会有一些属性,这些属性的值可能是另外一个对象,也可能是原生的数据类型:String, Number, Boolean, Null 或者 Undefined 。下面我们来看一下给对象属性赋值和读取对象属性的原理。

1
2
var o = new Object();
o.testNumber = 5;

上面的代码中我们先是创建了一个对象 o ,然后对其属性 testNumber 赋值。请注意,一开始的时候对象o是没有 testNumber 这个属性的,所以当赋值的时候 JS 会先检查,发现没有这个属性就先创建这个属性,然后再对其进行赋值。如果这个时候再执行下面的代码:

1
o.testNumber = 8;

JS 同样会先检查对象o ,发现它有testNumber这个属性之后,就不会再创建该属性,而是直接重新设置它的值。下面看一下读取对象属性:

1
2
o.testNumber = 8;
var val = o.testNumber;

读取对象属性的时候如果对象有这个属性就会返回它的值。很简单,不过有意思的地方在于它的内部机制 :) 首先我们先来了解一下原型链(prototype chain)的概念,所有的对象都会有一个原型(prototype),而它们的原型也是一个对象,这就是说它们的原型也可能有自己的原型,于是原型链就 形成了。在JS中,Object 的默认原型是 null ,所以:

1
var o = new Object();

会创建一个原型为 Object.prototype 的对象 o,由于 Object.prototype 的prototype 为空 ,所以对象 o 的原型链就只有一个,就是最原始的 Object.prototype 。但是下面的代码就复杂一点:

1
2
3
4
5
6
7
8
9
10
function MyObject1(formalParameter){ 
this.testNumber = formalParameter;
}
 
function MyObject2(formalParameter){
this.testString = formalParameter;
}
 
MyObject2.prototype = new MyObject1( 8 );
var objectRef = new MyObject2( "String_Value" );

objectRef 所指向的 MyObject2 的实例有一个原型链,原型链中的第一个对象是 MyObject1 的prototype,而 MyObject1 的 prototype 的 prototype 是最原始的 Object.prototype ,而Object.prototype的prototype为空,所以原型链至此结束。

OK。下面我们来继续说对象属性的读取,其实读取一个对象属性的过程可能会涉及到该对象原型链中的所有对象。在上面那段代码中,我们来读取下面的属性:

1
var val = objectRef.testString;

由于objectRef 所指向的 MyObject2 的实例上有这个名为 testString 的属性,所以就会直接把这个属性的值”String_Value”赋给变量 val 。但是:

1
var val = objectRef.testNumber;

我们会发现 val 会被赋值为 8 ,可是objectRef 所指向的 MyObject2 的实例上没有这个名为 testNumber 的属性啊?为什么val 并不会被赋值为 undefined 呢?因为在对象自身上找不到该属性的时候, 它就会检查对象的原型链 , 于是发现objectRef 指向的 MyObject2 的实例的prototype 是 MyObject1 的一个实例,而在这个实例中由一个值为 8 的 testNumber 属性,那么JS就会把这个值赋给 val 。我们看到,在 MyObject1 和 MyObject2 中都没有定义一个叫做 toString 的属性,但是当我们这样获取 objectRef 的 toString :

1
var val = objectRef.toString;

还是可以获取到。相信聪明的你已经想到了,因为 objectRef 的原型链的末端是 Object.prototype ,而在这个对象上面由一个名为 toString 的方法。最后:

1
var val = objectRef.madeUpProperty;

这次 val 终于是 undefined 了,因为检查完所有原型链都找不到名为 madeUpProperty 的属性,于是只好放弃。

综上,我们可以看到,在读取对象属性的时候,会先检查对象本身,然后依次检查它的原型链中的对象,在检查过程中发现符合要求的值就终止检查,然后返回。也就是说,它会返回 第一个 符合要求的值。了解了对象的赋值和读取过程之后,我们会发现一个有意思的事情,如果我们在上面代码的基础上执行:

1
objectRef.testNumber = 3;

由于objectRef 所指向的 MyObject2 的实例上没有 testNumber 这个属性,所以就会在该实例上创建一个名为 testNumber 的属性并赋值为 3 ,注意,这里并没有改变原型链上 MyObject1 的实例上的 testNumber 属性的值。而当我们再次获取 objectRef.testNumber 的值的时候,JS 会首先检查 objectRef 所指向的实例,然后就发现有这个属性,于是直接返回它的值:3 。这样一来,objectRef 原型链上的那个值为 8 的testNumber 属性就被隐藏了起来。

(第一部分就算完了,如果我的理解有什么偏差,欢迎拍砖,十分感谢!!)


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多