原型繼承(翻譯 vjeux 文章)

Javascript——原型繼承是如何實現的

在網頁上咱們隨處可見說javascript是一種原型繼承。然而javascript僅僅默認地在使用new操做符的這種特殊狀況下提供了原型繼承。所以,許多js的解讀都讓人感到很迷惑。 這篇文章用於闡述什麼是原型繼承以及如何在js中使用原型繼承。javascript

原型繼承的目的:在 js 中實現一種對象之間的資源(對象屬性)共享。html

原型繼承的定義

關於js的原型繼承,你常會看到以下定義:java

當訪問一個對象的屬性時,js會沿着原型鏈向上遍歷直到尋找到匹配的屬性。編程

大多數js實現使用 proto 屬性來表明原型鏈中的下一層對象。在這篇文章中咱們會看到 proto 和 prototype 究竟有什麼不一樣之處。app

注意: proto 是一種非標準表示方式,你不該該將其用在你的代碼中。在這篇文章中咱們用它來描述js繼承是如何實現的。編程語言

下面代碼顯示了js 引擎如何檢索屬性(讀取)。函數

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。
使用以前咱們所寫的原型繼承方法,咱們將建立一個 Point 對象以及它的三個屬性: x, y, 和 point。要建立一個新的點,咱們只須要使用設置在 Point對象上的 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奇怪的原型繼承

讓人迷惑的是每一個教授js原型繼承的人都給了這樣的定義卻沒有給如上的代碼。他們所給的代碼像下面這樣:優化

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 操做符。這是什麼鬼東西?ui

new 是如何工做的

Brendan Eich 想要將 Javascript 設計成一種傳統的面向對象的編程語言,如同 Java h和 C++ 那樣。在這些語言中,咱們使用 new 操做符來建立一個類的實例。因此他爲 Javascript 添加了 new 操做符。

C++ 有構造函數( constructor )的概念,用於初始化實例屬性。所以,new 操做符必須的做用對象必須是一個函數。
咱們必須把對象的方法放在某一處。因爲咱們使用的是一種原型語言,讓咱們把它置於函數的原型屬性中。

new 操做符建立一個帶參數的 F 函數: new F(arguments...)。經過以下三步來實現:

  1. 建立類的實例: 這是一個將 proto 屬性設置在 F.prototype 上的空對象。
  2. 初始化實例: F函數調用時接受傳入的參數而且將 this 指向這個實例。
  3. 返回實例
    如今咱們理解了 new 操做符是怎樣工做的,咱們能夠在 js 中實現它。
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
複製代碼

js中真正的原型繼承

js規範僅爲咱們提供了 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
複製代碼

結論

咱們看到了究竟什麼是原型繼承而且看到了 js 僅僅是用了一種特別的方法來實現它。
然而,使用原型繼承(Object.create and proto)也存在一些缺點:

  • 非標準proto 不是標準化的東西甚至被棄用。原生的 Object.create 和 Douglas Crockford 的實現也不徹底相同。
  • 未優化:與 new 構造函數(原生或自定義)相比Object.create 尚未被深度嚴格優化, 後者甚至慢十倍。

另外

若是你可以經過下面的圖理解原型繼承是如何工做的,Congratulations!

Vjeux英文原文連接

相關文章
相關標籤/搜索