Person.prototype是一个可以被Person的所有实例共享的对象。它是一个名叫原型链(prototype chain)的查询链的一部分:当你试图访问一个Person没有定义的属性时,解释器会首先检查这个Person.prototype来判断是否存在这样一个属性。所以,任何分配给Person.prototype的东西对通过this对象构造的实例都是可用的。
原型组成链的一部分。那条链的根节点是Object.prototype,它包括toString()方法——将对象转换成字符串时调用的方法。
闭包
下面我们将看到的是 JavaScript 中必须提到的功能最强大的抽象概念之一:闭包。但它可能也会带来一些潜在的困惑。那它究竟是做什么的呢?
functionmakeAdder(a){returnfunction(b){return a+ b;}}var x=makeAdder(5);var y=makeAdder(20);x(6);// ?y(7);// ?
makeAdder这个名字本身应该能说明函数是用来做什么的:它创建了一个新的adder函数,这个函数自身带有一个参数,它被调用的时候这个参数会被加在外层函数传进来的参数上。
这里发生的事情和前面介绍过的内嵌函数十分相似:一个函数被定义在了另外一个函数的内部,内部函数可以访问外部函数的变量。唯一的不同是,外部函数被返回了,那么常识告诉我们局部变量“应该”不再存在。但是它们却仍然存在——否则adder函数将不能工作。也就是说,这里存在makeAdder的局部变量的两个不同的“副本”——一个是a等于5,另一个是a等于20。那些函数的运行结果就如下所示:
x(6);// 返回 11y(7);// 返回 27
下面来说说到底发生了什么。每当 JavaScript 执行一个函数时,都会创建一个作用域对象(scope object),用来保存在这个函数中创建的局部变量。它和被传入函数的变量一起被初始化。这与那些保存的所有全局变量和函数的全局对象(global object)类似,但仍有一些很重要的区别,第一,每次函数被执行的时候,就会创建一个新的,特定的作用域对象;第二,与全局对象(在浏览器里面是当做window对象来访问的)不同的是,你不能从 JavaScript 代码中直接访问作用域对象,也没有可以遍历当前的作用域对象里面属性的方法。
所以当调用makeAdder时,解释器创建了一个作用域对象,它带有一个属性:a,这个属性被当作参数传入makeAdder函数。然后makeAdder返回一个新创建的函数。通常 JavaScript 的垃圾回收器会在这时回收makeAdder创建的作用域对象,但是返回的函数却保留一个指向那个作用域对象的引用。结果是这个作用域对象不会被垃圾回收器回收,直到指向makeAdder返回的那个函数对象的引用计数为零。
作用域对象组成了一个名为作用域链(scope chain)的链。它类似于原型(prototype)链一样,被JavaScript 的对象系统使用。
一个闭包就是一个函数和被创建的函数中的作用域对象的组合。
prototype和Object.getPrototypeOf
对于从 Java 或C++ 转过来的开发人员来说 JavaScript 会有点让人困惑,因为它全部都是动态的,都是运行时,而且不存在类(classes)。所有的都是实例(对象)。即使我们模拟出的 “类(classes)”,也只是一个函数对象。
你可能已经注意到我们的function A有一个叫做prototype的特殊属性。该特殊属性可与 JavaScript 的new 操作符一起使用。对原型对象的引用被复制到新实例的内部[[Prototype]]属性。例如,当执行var a1= new A()时,JavaScript(在内存中创建对象之前,并且在运行函数A()之前)定义了它)设置a1.[[Prototype]] = A.prototype。然后当您访问实例的属性时,JavaScript首先会检查它们是否直接存在于该对象上,如果不存在,则会[[Prototype]]中查找。这意味着你在prototype中定义的所有内容都可以由所有实例有效共享,你甚至可以稍后更改部分prototype,并在所有现有实例中显示更改(如果需要)。
像上面的例子中,如果你执行var a1 = new A(); var a2 = new A(); 那么 a1.doSomething事实上会指向Object.getPrototypeOf(a1).doSomething,它就是你在A.prototype.doSomething中定义的内容。比如:Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething。
简而言之,prototype是用于类型的,而Object.getPrototypeOf()是用于实例的(instances),两者功能一致。
[[Prototype]]看起来就像递归引用,如a1.doSomething,Object.getPrototypeOf(a1).doSomething,Object.getPrototypeOf(Object.getPrototypeOf(a1)).doSomething等等等,直到它被找到或Object.getPrototypeOf返回null。
因此,当你执行:
var o=new Foo();
JavaScript 实际上执行的是:
var o=new Object();o.__proto__= Foo.prototype;Foo.call(o);
(或者类似上面这样的),然后当你执行:
o.someProp;
它检查o是否具有someProp属性。如果没有,它会查找Object.getPrototypeOf(o).someProp,如果仍旧没有,它会继续查找Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp。