原文链接
Javascript原型继承在我们所能阅读到的网络内容里面无处不在。然而Javascript通过new运算符提供了一个默认的原型继承实例。因此,大多数的解释都令人阅读起来很费力。本文旨在阐明什么是原型继承以及如何在Javascript中使用原型继承。
原型继承的定义
当你在阅读关于Javascript原型继承资料时,你经常能看到如下定义:
当访问一个对象的属性时,JavasScript将会向上对原型链进行横切直到找到请求的名称的属性。Javascript Garden
大多数的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
}
举一个常用的例子:2D Point(二维点)。一个点拥有两个坐标值 x,y,以及一个print方法。使用之前的关于原型继承的书面定义,我们将会创建一个带有x,y,print等三个属性的二维点对象。为了创建这个新的二维点,我们只是创建了一个带有__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独特的原型继承
令人疑惑的是任何人教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现在是一个方法,我们用一个原型属性,一个new运算符。搞什么鬼!?
new是怎么工作的
布兰登·艾奇希望Javascript看起来像传统的面向对象语言一样如Java和C++。在这种情况下,我们通过new操作符来创建类的新实例。于是他给Javscript写了一个new的操作。
* C++有构造器的概念,构造器用来初始化实例属性。因此new操作符必须指向一个函数。
* 我们需要把一个方法放置于对象的某处。因为我们工作在一个原型语言上,所以我们把它放在函数的prototype属性上。
new运算符接收函数F与参数:new F(arguments…)。它只做了三个简单的步骤:
1. 创建一个类的实例。 这是一个空对象只带有自身的__proto__属性,并设置为F.prototype。
2. 初始化实例。 函数F被调用时需要传递参数以及这个用于赋值的的实例。
3. 返回实例
现在我们了解了new操作符的工作原理,我们就可以再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发现了一种利用new操作符来实现真正的原型继承的方法!他编写了Object.create方法。
Object.create = function (parent) {
function F() {}
F.prototype = parent;
return new F();
};
这看上去很奇怪,但它做的真的很简单。它只是创建了一个新的对象,且其原型可以设置为任何你想要的。如果我们允许使用__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
结论
我们了解了什么原型继承以及javascript是如何通过仅有的一种特殊方式来实现的。
但是,使用真正的原型继承(Object.create 与 __proto__)还是有一些缺点的:
* 不规范 :__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规范),你将会得到一个免费的cookie!
反馈与建议
如有翻译错误之处,欢迎指正。
- 邮箱:alpherkingsly@sina.com.cn