js實現繼承的幾種方式

litongqian發表於2018-02-28

經常瀏覽各種文章,學習各種技術,看了,不用,忘了.

好記性,不如爛筆頭!

繼承概念:

繼承機制例項

說明繼承機制最簡單的方式是,利用一個經典的例子 - 幾何形狀。實際上,幾何形狀只有兩種,即橢圓形和多邊形。圓是橢圓的一種,它只有一個焦點。三角形、矩形和五邊形都是多邊形的一種,具有不同數量的邊。這就構成了一種完美的繼承關係。

在這個例子中,形狀 是橢圓形和多邊形的基類(base class)(所有類都由它繼承而來)。圓形繼承了橢圓形,因此圓形是橢圓形的子類(subclass),橢圓形是圓形的超類(superclass)。同樣,三角形、矩形和五邊形都是多邊形的子類,多邊形是它們的超類。最後,正方形(Square)繼承了矩形。

下面的圖示是解釋 Shape 和它的子類之間關係的 圖示:

繼承機制 UML 圖示例項


在講解繼承之前先了解一下JavaScript中建立一個物件的過程

function Person(name){
    //呼叫new的過程相當於 var this=new Object();
    //給this物件賦值
    //最後return this;
    this.name=name;
};
Person.prototype.getName=function(){
return this.name;
};
var a=new Person("seven");
console.log(a.name); //輸出:seven
console.log(a.getName); //輸出:seven

console.log(Object.getPrototypeOf(a)===Person.prototype); //輸出:true
複製程式碼

javaScript的函式可以被當做普通函式呼叫,也可以作為構造器呼叫.


javaScript中的原型繼承

原型程式設計的規則:

  • 1.所有資料都是物件
  • 2.要得到一個物件不是例項化類,而是找到一個物件作為原型並克隆ta
  • 3.物件會記住它的原型(__proto__)
  • 4.如果物件無法響應某個請求,ta會把這個請求委託給自動的建構函式的原型


繼承的方式

ECMAScript 實現繼承的方式不止一種。這是因為 JavaScript 中的繼承機制並不是明確規定的,而是通過模仿實現的。這意味著所有的繼承細節並非完全由解釋程式處理。作為開發者,你有權決定最適用的繼承方式。

物件冒充

因為建構函式只是一個函式,所以可使 ClassA 建構函式成為 ClassB 的方法,然後呼叫它。ClassB 就會收到 ClassA 的建構函式中定義的屬性和方法。例如,用下面的方式定義 ClassA 和 ClassB:

function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}
function ClassB(sColor) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;
}複製程式碼

在這段程式碼中,為 ClassA 賦予了方法 newMethod(請記住,函式名只是指向它的指標)。然後呼叫該方法,傳遞給它的是 ClassB 建構函式的引數 sColor。最後一行程式碼刪除了對 ClassA 的引用,這樣以後就不能再呼叫它。

所有新屬性和新方法都必須在刪除了新方法的程式碼行後定義。否則,可能會覆蓋超類的相關屬性和方法:

function ClassB(sColor, sName) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;

    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}複製程式碼

為證明前面的程式碼有效,可以執行下面的例子:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//輸出 "blue"
objB.sayColor();	//輸出 "red"
objB.sayName();		//輸出 "John"複製程式碼

可以使用Call方法優化一下

function ClassB(sColor, sName) {
    //this.newMethod = ClassA;
    //this.newMethod(color);
    //delete this.newMethod;

    ClassA.call(this, sColor);
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}複製程式碼

這種方式只是ClassB呼叫了ClassA,給ClassB的this賦值

原型鏈(prototype chaining)

function ClassA() {
}

ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB() {
}

ClassB.prototype = new ClassA();

複製程式碼

原型方式的神奇之處在於最後一行程式碼。這裡,把 ClassB 的 prototype 屬性設定成 ClassA 的例項。因為想要 ClassA 的所有屬性和方法,但又不想逐個將它們 ClassB 的 prototype 屬性。還有比把 ClassA 的例項賦予 prototype 屬性更好的方法嗎?

呼叫 ClassA 的建構函式,沒有給它傳遞引數。這在原型鏈中是標準做法。要確保建構函式沒有任何引數。

與物件冒充相似,子類的所有屬性和方法都必須出現在 prototype 屬性被賦值後,因為在它之前賦值的所有方法都會被刪除。為什麼?因為 prototype 屬性被替換成了新物件,新增了新方法的原始物件將被銷燬。所以,為 ClassB 類新增 name 屬性和 sayName() 方法的程式碼如下:

function ClassB() {
}

ClassB.prototype = new ClassA();

ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
    alert(this.name);
};
複製程式碼

可通過執行下面的例子測試這段程式碼:

var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName()複製程式碼

混合方式

建立類的最好方式是用建構函式定義屬性,用原型定義方法。這種方式同樣適用於繼承機制,用物件冒充繼承建構函式的屬性,用原型鏈繼承 prototype 物件的方法。用這兩種方式重寫前面的例子,程式碼如下:

function ClassA(sColor) {
    this.color = sColor;
}

ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB(sColor, sName) {
    ClassA.call(this, sColor);//物件冒充
    this.name = sName;
}

ClassB.prototype = new ClassA();//原型

ClassB.prototype.sayName = function () {
    alert(this.name);
};複製程式碼

在 ClassB 建構函式中,用物件冒充繼承 ClassA 類的 sColor 屬性。用原型鏈繼承 ClassA 類的方法。

ES6 實現了Class,可以通過關鍵字extends實現繼承

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);    this.color = color; // 正確
  }
}複製程式碼

轉換成es5

"use strict";

function _possibleConstructorReturn(self, call) { 
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

function _inherits(subClass, superClass) { 
  subClass.prototype = Object.create(
      superClass && superClass.prototype, 
      { 
      constructor: { 
        value: subClass, 
        enumerable: false, 
        writable: true, 
        configurable: true 
      } 
      }
  );
    if (superClass) {
      Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
    }
  }

var Point = function Point(x, y) {
  this.x = x;
  this.y = y;
};

var ColorPoint = function (_Point) {
  _inherits(ColorPoint, _Point);

  function ColorPoint(x, y, color) {
    var _this = _possibleConstructorReturn(this, (ColorPoint.__proto__ || Object.getPrototypeOf(ColorPoint)).call(this, x, y));

    _this.color = color; // 正確
    return _this;
  }

  return ColorPoint;l
}(Point);複製程式碼

這裡重點講解一下ES5中的繼承

1. _inherits,首先使用Object.create將父類Point原型拷貝到子類ColorPoint上

2.將父類建構函式掛載到子類的__proto__ 上

,保持對父類的應用

3.當呼叫子類
ColorPoint,初始化時呼叫ColorPoint.__proto__.call(this, x, y),將父類中的屬性掛載到子類的this中

非建構函式的繼承

比如,現在有一個物件,叫做"中國人"。

  

var Chinese = {
    nation:'中國'
  };複製程式碼

還有一個物件,叫做"醫生"。


 var Doctor ={
    career:'醫生'
  }複製程式碼

請問怎樣才能讓"醫生"去繼承"中國人",也就是說,我怎樣才能生成一個"中國醫生"的物件?

可以利用空建構函式:

function extends(o) {
    function F() {}
    F.prototype = o;
    return new F();
  }
var Doctor = object(Chinese);
Doctor.career = '醫生';
複製程式碼

也可以使用Object.create()

var Doctor = Object.create(Chinese);
Doctor.career = '醫生';複製程式碼

到此結束!


相關文章