【翻译】javascript原型继承到底是如何实现的
写文章之前先讲个笑话,活跃一下气氛——
校长:“先生,我有一个好消息,一个坏消息,都是关于您儿子的。” 家长:“校长,先说坏消息吧!”
校长:“坏消息是,您的儿子的动作十分女性化。” 家长:“那好消息呢?” 校长:“好消息是他现在是本校校花。”
言归正传,先放原文地址:
http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
这是我第一次翻译别人的文章,不到之处,敬请谅解。
全文:
javascript——原型继承到底是如何实现的
在网上,我们到处都看到javascript是基于原型的继承。尽管如此,javascript默认仅仅只是通过new操作符提供一种特殊形式的原型继承
因此,许多言论和解释都是难以理解的。本文就是剖析原型继承的实质以及如何使用原型继承。
原型继承定义
当我们遇到原型继承时,我们通常会看到如下的一段解释:
当需要读取一个对象的属性时,javascript会沿着原型链向上寻找直到发现吻合的属性为止。
在多数的javascript实现中,__proto__用来表示原型链的下一个对象,
在接下来的内容中,我们会发现__proto__与prototype的不同之处。
Note:__proto__是非标准的 ,因此不应该出现在你的正式代码中,本文使用这个属性,只为明理,而非实用。
下面的代码揭示了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
}
我们举一个通俗的例子:二位坐标点。一个点有x、y两个坐标和一个print方法。
遵循上面的原型继承定义,我们来生成一个具有3个属性(x、y、print)的对象,为了创建一个新的点,用__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怪异的原型继承
让人迷惑的是,每一个引用上面的定义来教授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的发明人)想让javascript看起来与一些传统的面向对象语言(比如Java,C++)跟接近,在哪些传统语言中,通过new操作符来创建一个类的实例,因此,他也跟风实现了new操作符。
C++有构造函数的概念,用来初始化一个实例属性。因此,new操作符必须用在一个函数上。
对象的方法需要有地方存放,鉴于我们在使用原型式语言,我们就把他放在函数的prototype属性里面
new操作符就像含有一个F函数,以及参数new F(arguments),几个步骤如下:
1、创建一个类的实例,得到的是一个__proto__指向F。protoype的空对象。
2、初始化前面得到的空对象,也就是实例。在这一步,F函数被调用,并且将参数传递进来,将this指向第一步的实例。
3、返回这个实例
可能说的不好理解,我们用代码方式来实现他的的步骤:
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
javacsript中真正的原型继承
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 };
};
下面用真正的原型继承来改写我们上面的坐标点例子:
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__)使用起来还是有一些缺点的:
1、不是标准方法:__proto__是不标准甚至是已过时的,原生的Object.create方法与Douglas Crockford的实现并不是完全等价的。
2、不够优化:Object.create (不管是原生的还是模仿的)都不如new操作符优化,速度差距可能达到10倍。