日期:2014-05-16  浏览次数:20430 次

JavaScript原型的工作原理(以及如何利用它来实现类的继承)

原文:How JavaScript Prototype Really Works


本文是JavaScript技术系列的第二篇,这次我打算深入讨论下最令人困惑的JavaScript的原型对象,以及如何用它来实现继承。


在上一篇文章里,我们以及详细分析过构造函数,还有怎样将其变成面向对象语言里的类。但是JavaScript不是基于类的编程语言,它是基于原型的编程语言。这到底是什么意思呢?简单来说,JavaScript不是通过类来创建对象,而是通过对象来创建对象。




JavaScript:一种基于原型的语言(prototype-based language)


在传统的基于类实现面向对象的语言里(比如Java或者C++),在创建一个对象之前,你要先创建一个类来定义这个对象的属性和方法。于是这个类就成为用来创建对象的蓝图,就好像在现实中依照真的蓝图来盖房子一样。


你定义好了类之后,便马上可以用它来创建对象了。我们通过new关键字来根据类创建一个相应的对象,对于这一过程,我们有个绝妙的称呼:类的实例化。新创建的对象将会具有类定义的所有的属性和方法。在传统的基于类的语言里,我们可以这样做:

public class Shape {
	private int height;
	private int width;
	public Shape(int h, int w) {
		height = h;
		width = w;
	}
	public int area() {
		return height * width;
	}
}
Shape shape = new Shape(10, 2);
System.out.println(shape.area());  // 20


JavaScript没有类,有的只是对象和基本数据类型。为简化问题起见,我们姑且说JavaScript里的所有东西都是对象,从实数到字符串,甚至是函数本身。前一篇文章已经讲到过,有两种创建对象的方法:一种是从字面上直接创建(有些地方称为通过JSON符号创建),另一种方法是通过构造函数。由于本文的主旨,我们以后将只考虑后者。


在能够创建对象之前,你需要先创建一个函数(也就是说构造函数)。接下来在用new关键字创建对象的时候,这个函数会充当蓝图使用。新创建的对象上面会拷贝所有在函数体内定义的属性和方法。这便是原型语言如何用对象来创建对象的(译者注:函数就是个function类型的对象嘛):

function Shape() {
	this.height = 10;
	this.width = 10;
	this.area = function() {
		return this.height * this.width;
	};
};
var shape = new Shape();
console.log(shape.area());  // 100


但是,也有很多属性和方法并没有在相应的构造函数中定义,可是用它们所创建的对象却仍然可以访问到。比如说toString()方法。任何对象都可以用这个方法输出一个关于自身的字符串。Shape()函数体内并没定义这个方法,但是shape对象却仍然可以使用它。
console.log(shape.toString());  // "[object Object]";


那么,对象是从哪里得到这些方法的呢?答案是从JavaScript的原型那里。




JavaScript的原型



JavaScript的每一个对象都有一个通常是被隐藏了的属性,被称作[[Prototype]]。[[Prototype]]属性的值实际上是一个指针,它指向一个对象,当JavaScript在当前对象上面找不到一个属性的时候,便会委托(delegation)给这个对象(译者注:就是[[Prototype]]指向的东东)。委托(delegation)的意思就是当你做不了一件事情的时候,你叫别人代替你去做。


当你要在一个对象上访问一个属性时,JavaScript首先去看看这个对象本身上面是否有这个属性。如果没有,JavaScript就会去查找它的[[Prototype]]值,去看看这个值所指向的对象上面是否有被请求的这个属性。这个操作会一直持续下去,直到找到这个属性或者最终[[Prototype]]返回null值为止。这就是所谓的原型链了,将对象都链接起来,每一个对象都是这个链条中的一个节点。在大多数浏览器里,一个对象的[[Prototype]]属性可以通过一个非标准方法来访问,那就是一个叫__proto__的属性,不过除此以外,另外还有一个办法一般都可行,就是用Object.isPrototypeOf()方法。


现在回到前面说的shape.toString()的例子。当我们要在shape对象上面呼叫toString()方法时,JavaScript首先在shape对象上面寻找这个方法,可是却发现它并没有。于是JavaScript开始查找shape的[[Prototype]]值指向的另一个对象。而在这个对象上也没有toString()方法。于是继续查找这个对象的[[Prototype]]值所指向的又一个新对象。最后,JavaScript终于在这上面找到了toString()方法,于是JavaScript使用这个方法,并终止了原型链查找过程。





现在我们用hasOwnProperty()方法来验证一下上述推论,每个对象都拥有这个方法。如果一个对象拥有某个属性,那么hasOwnProperty()将会返回true,否则返回false。