JavaScript-原型繼承工作原理


【翻譯】JavaScript原型繼承工作原理

【翻譯】JavaScript原型繼承工作原理



Javascript – 如何真正實現原型鏈繼承

本文轉載自:衆成翻譯
譯者:Daguo
鏈接:http://www.zcfy.cc/article/1337
原文:http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html

在網上的很多地方我們可以得知javascript是基於原型鏈繼承的,其實Javascript只提供一種特殊的方法來實現原型鏈繼承,就是通過new操作。但是很多解讀都令人難以理解,這篇文章旨在說明到底什麼是原型鏈繼承以及如何在Javascript中真正地運用它。

原型鏈繼承的定義

當你讀到有關於Javascript原型鏈繼承的內容時,經常看到這樣的定義:

當對象存取一個屬性時,Javascript會沿着原型鏈向上查詢直到找到要存取的屬性名(或原型鏈頂端)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的點,一個點有兩個座標值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操作符必須指向一個函數。
  • 我們需要在一些地方用到對象的方法,由於我們使用的是一門原型語言,可將方法放在函數的原型屬性上。

new操作符創建函數F和參數arguments : new F(arguments…),進行簡單的三步操作:

  1. 創建一個實例,它是一個空對象,其__proto__屬性被設爲F.prototype。
  2. 實例初始化,將函數F的參數傳遞到實例上,F的this(上下文)被設爲指向實例。
  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 specifications 只給了我們一種基於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

結論

我們已經知道了什麼是原型鏈繼承以及Javascript怎樣通過一種特殊的方式實現它。

但是,這種原型鏈繼承方法(Object.create and __proto__) 有一些缺點:

  • 沒有標準: __proto__不是標準甚至是被反對的. 包括通用的Object.create和Douglas Crockford實踐都沒有確定的標準形式。
  • 沒有優化: Object.create比起new操作方式顯得更加笨重,這一點被證明於10 times slower.

參考閱讀:


發佈了51 篇原創文章 · 獲贊 33 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章