原文: http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
在网络到处可以看到Javascript是原型继承,但Javascript只提供了使用new关键字来实现原型继承,所以很多文章读后有诸多的疑问。这篇文章目的就是要说明Javascript的原型继承是如何工作的。
原型继承定义
原型继承大多被定义为:
当访问对象的属性时,Javascript会向上遍历原型链,直到找到相应的属性 JavascriptGarden
许多Javascript的实现都使用__proto__这个属性表示原型链中的下一个对象,接着我们讨论一下__proto__与prototype 的区别。
注意:__proto__是非标准,在代码中别使用。这里只是说明Javascript的继承是如何工作的。
Javascript如何获取一个属性:
function getProperty(obj, prop) {
if (obj.hasOwnProperty(prop))
return obj[prop]
else if (obj.__proto__ !== null)
return getProperty(obj.__proto__, prop)
else
return undefined
}
使用Point做个例子,它有两个属性x, y并有一个方法print。在使用原型继承之前,先创建一个Point,并把它做为属性__proto__的值传给一个新的对象。
var Point = {
x: 0,
y: 0,
print: function () { console.log(this.x, this.y); }
};
var p = {x: 10, y: 20, __proto__: Point};
p.print(); // 10 20
Javascript怪异的原型继承
许多人在讲原型继承时并没有给出上面的代码,却都给出下面类似的代码:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
print: function () { console.log(this.x, this.y); }
};
var p = new Point(10, 20);
p.print(); // 10 20
上面两段代码是完全不同的,Point是个函数,并且我们使用了prototype属性和关键字new, 但到底是如何执行的呢?
new 是如何工作的
BrendanEich 想让Javascript像传统面向对象语言(Java, C++)一样使用new来创建一个对象。所以他在Javascript中定义了关键字new。
- 在C++在有构造器的概念,构造器会初始化实例的一些属性,所以构造器必须是一个函数。
- 因为Javascript是一种原型语言,所以把prototype做为函数的一个属性。
使用new,需要函数名F和参数:new F(arguments…)。这个过程做了三件事情:
- 创建了一个实例。这个实例是一个具有__proto__属性的空对象,并且__proto__指向F.prototype
- 初始化实例。将arguments 和 this赋予函数F。
- 返回这个实例。
用Javascript实现如下:
function New (f) {
/*1*/ var n = { '__proto__': f.prototype };
return function () {
/*2*/ f.apply(n, arguments);
/*3*/ return n;
};
}
测试:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
print: function () { console.log(this.x, this.y); }
};
var p1 = new Point(10, 20);
p1.print(); // 10 20
console.log(p1 instanceof Point); // true
var p2 = New (Point)(10, 20);
p2.print(); // 10 20
console.log(p2 instanceof Point); // true
Javascript中真正的原型继承
Javascript只有关键字new,并没有与继承相关的关键字,但Douglas Crockford给出了一种实现原型继承的方法。代码如下:
Object.create = function (parent) {
function F() {}
F.prototype = parent;
return new F();
};
很奇怪吧,非常简单,就是创建了一个新对象,并把parent赋给F.prototype。假如__proto__是个关键字,那么上面的代码可以简写为:
Object.create = function (parent) {
return { '__proto__': parent };
};
改写Point的例子:
var Point = {
x: 0,
y: 0,
print: function () { console.log(this.x, this.y); }
};
var p = Object.create(Point);
p.x = 10;
p.y = 20;
p.print(); // 10 20
结论
我们明白了什么是原型继承并且是如何工作的。
但使用原型继承(Object.create()和__proto__)有缺陷:
- 非标准:__proto__并非标准,并且已经废弃。还有,Javascript中Object.create的实现和Douglas Crockford的实现并不完全一样。
- 没有优化:Object.create(本地实现和用户实现)都没有new的效率高,要慢10倍以上。
其它文章:
- Douglas Crockford: Prototypal Inheritance
- MDC Documentation: __proto__
- John Resig: getPrototypeOf
- Javascript Garden: Object.prototype
- Dmitry Shoshnikov: OOP: ECMAScript Implementation
- Angus Croll: Understanding Javascript prototypes
- Yehuda Katz: Understanding JavaScript Function Invocation and “this”