您的位置:首页 > Web前端 > JavaScript

Javascript – 原型继承真正的工作方式(by Vjeux)

2014-05-18 20:34 309 查看
作者:Vjeux

原文链接:点击打开链接

JavaScript有原型继承,在几乎任意的关于web开发的文章中我们在都能读到。然而,JavaScript仅仅提供了默认的用new操作符实现特定情况下的原型继承。因此,大多数的解释真的让人感到很困惑。这篇文章的目的在于澄清原型继承到底是什么以及如何在JavaScript中真正的应用她 。

原型继承的定义

当你在读原型继承的定义的时候,她经常是这样子的:

当你想获取一个对象的属性的时候,JavaScript会沿着原型链往上直到她找到该名字的属性。

绝大多数的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的坐标点。这个点有两个坐标值: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现在是一个函数,并且用了一个prototype属性,new操作符。这究竟是在搞什么?

new是如何工作的

Brendan Eich想让JavaScript看起来像面向对象编程语言那样,比如Java 和 C++。为此,我们用new来创建一个类的实例。因此他为JavaScript写了new操作符。

*C++有构造函数的概念,她初始化了实例的属性。因此,new操作符的目标必须是一个函数。

*我们需要把方法放在对象的某个地方。因为我们正在研究一个原型语言,因此让我们把它放在函数的prototype属性上。

new操作符需要一个F函数和参数:new F(arguments...)。

她简单的执行三步工作:

1、创建类的实例。这个实例是一个空对象,它的__proto__属性被设置为F.prototype。

2、初始化这个实例。函数F被传入参数调用并且this被设置为这个实例。

3、返回该实例。

现在我们理解了new操作符做了哪些事情,我们也可以在JavaScript中实现她:

function New (f) {
var n = { '__proto__': f.prototype };
return function () {
f.apply(n, arguments);
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 specifications中仅仅给出了new操作符操作了什么。然而Douglas Crockford发现了一种方式来扩展new以实现真正的原型继承。他写了Object,create函数(链接:http://javascript.crockford.com/prototypal.html)。

Object.create = function (parent) {
function F() {}
F.prototype = parent;
return new F();
};


她看起来很奇怪但是真的很简单。她只是创建了一个新对象并且把他的prototype属性设置为任何你想要的。如果允许使用__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 (native or custom) 还没有像new结构那样的被最优化,她会慢十倍甚至更多

额外的福利

如果你能从下面的图中(from the ECMAScript standard) 理解原型继承到底是怎么工作的,你就获得了一份免费的“甜圈”!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: