- 原文地址:Javascript – How Prototypal Inheritance really works
- 原文作者:vjeux
- 永久链接:译JavaScript原型继承原理
众所周知,JavaScript有原型继承性。但通常情况下,原型继承是通过new操作符实现的,大部分的解释都让人困惑,这篇文章旨在澄清什么是原型继承以及如何使用。
原型继承定义
JavaScript原型通常是这样定义的:
When accessing the properties of an object, JavaScript will traverse the prototype chain upwards until it finds a property with the requested name. Javascript Garden
JavaScript通过__proto__属性来代表原型链中下一个对象。跟着这片文章,让我们看看__proto__和prototype的区别。
说明:
__proto__是JavaScript非标准属性,最好不要在你的代码中用到。当然,这篇文章是用来解释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__属性指向Point
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 到底做了什么
Brendan Eich 在创造JavaScript 时,想让它看起来像传统的面向对象语言,类似于Java、C++。在这些语言中,使用了 new 操作符来创建一个类的实例。所以,他也写了一个JavaScript的new操作符:
- C++有构造函数的概念,用来初始化对象实例的属性,因此
new操作符的对象应该是一个函数。 - 我们需要把对象的方法放在某个地方,由于我们正在使用原型语言,因此将其放在函数的
prototype属性中。
new 操作符接收一个函数F和参数arguments: new F(arguments...),总共做了三件事:
- 创建类的实例。它是一个空对象,其
__proto__属性设置为F.prototype - 实例初始化。带上参数,调用这个方法
F,并把this绑定到实例上。 - 返回实例。
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 找到一种方法,利用new操作符实现了真正的原型继承。
他创建了 Object.create 方法:
Object.create = function (parent) {
function F() {}
F.prototype = parent;
return new 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
结论
我们已经了解了什么是原型继承,以及实现原型继承的几种方式。
但是,使用__proto__和 Object.create()有一些缺点:
- 非标准化:
__proto__是非标准化的属性,甚至会被废弃。原生的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”
彩蛋
如果你能看懂下面这张图(来自 ECMAScript 标准),你将会有意想不到的收获!
