(翻译)Javascript-How Prototypal Inheritance really works

原文链接
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值