《JavaScript面向對象精要》之五:繼承

5.1 原型對象鏈和 Object.prototype

JavaScript 內建的繼承方法被稱爲 原型對象鏈(又叫原型對象繼承)。app

原型對象的屬性可經由對象實例訪問,這就是繼承的一種形式。ide

對象實例繼承了原型對象的屬性,而原型對象也是一個對象,它也有本身的原型對象並繼承其屬性,以此類推。這就是原型對象鏈。函數

全部對象(包括自義定的)都自動繼承自 Object,除非你另有指定。更確切地說,全部對象都繼承自 Object.prototype。任何以對象字面量形式定義的對象,其 [[Prototype]] 的值都被設爲 Object.prototype,這意味着它繼承 Object.prototype 的屬性。ui

5.1.1 繼承自 Object.prototype 的方法

Object.prototype 通常有如下幾個方法:this

  • hasOwnProperty() 檢測是否存在一個給定名字的自有屬性
  • propertyIsemumerable() 檢查一個自有屬性是否可枚舉
  • isPrototypeOf 檢查一個對象是不是另外一個對象的原型對象
  • valueOf() 返回一個對象的值表達
  • toString() 返回一個對象的字符串表達

這 5 種方法經由繼承出如今全部對象中。 由於全部對象都默認繼承自 Object.prototype,因此改變它就會影響全部的對象。因此不建議。spa

5.2 繼承

對象繼承是最簡單的繼承類型。你惟須要作的是指定哪一個對象是新對象的 [[Prototype]]。對象字面量形式會隱式指定 Object.prototype 爲其 [[Protoype]]。固然咱們能夠用 ES5 的 Object.create() 方法顯式指定。該方法接受兩個參數,第一個是新對象的 [[Prototype]] 所指向的對象。第二個參數是可選的一個屬性描述對象,其格式與 Object.definePrototies()同樣。prototype

var obj = {
  name: "Ljc"
};

// 等同於
var obj = Object.create(Object.prototype, {
  name: {
    value: "Ljc",
    configurable: true,
    enumberable: true,
    writable: true
  }
});
複製代碼

下面是繼承其它對象:code

var person = {
  name: "Jack",
  sayName: function(){
    console.log(this.name);
  }
}

var student = Object.create(person, {
  name:{
    value: "Ljc"
  },
  grade: {
    value: "fourth year of university",
    enumerable: true,
    configurable: true,
    writable: true
  }
});

person.sayName(); // "Jack"
student.sayName(); // "Ljc"

console.log(person.hasOwnProperty("sayName")); // true
console.log(person.isPrototypeOf(student)); // true
console.log(student.hasOwnProperty("sayName")); // false
console.log("sayName" in student); // true
複製代碼

當訪問一個對象屬性時,JavaScript 引擎會執行一個搜索過程。若是在對象實例存在該自有屬性,則返回,不然,根據其私有屬性 [[Protoype]] 所指向的原型對象進行搜索,找到返回,不然繼承上述操做,知道繼承鏈末端。末端一般是 Object.prototype,其 [[Prototype]]null對象

固然,也能夠用 Object.create() 常見一個 [[Prototype]]null 的對象。繼承

var obj = Object.create(null);

console.log("toString" in obj); // false
複製代碼

該對象是一個沒有原型對象鏈的對象,便是一個沒有預約義屬性的白板。

5.3 構造函數繼承

JavaScript 中的對象繼承也是構造函數繼承的基礎。

第四章提到,幾乎全部函數都有 prototype 屬性,它可被修改或替換。該 prototype 屬性被自動設置爲一個新的繼承自 Object.prototype 的泛用對象,該對象(原型對象)有一個自有屬性 constructor

實際上,JavaScript 引擎爲你作了下面的事情。

// 你寫成這樣
function YourConstructor(){
  // initialization
}

// JavaScript引擎在背後爲你作了這些處理
YourConstructor.prototype = Object.create(Object.prototype, {
  constructor: {
    configurable: true,
    enumerable: true,
    value: YourConstructor,
    writable: true
  }
})
複製代碼

你不須要作額外的工做,這段代碼幫你把構造函數的 prototype 屬性設置爲一個繼承自 Object.prototype 的對象。這意味着 YourConstructor 建立出來的任何對象都繼承自 Object.prototype

因爲 prototype 可寫,你能夠經過改變它來改變原型對象鏈。

instanceof 運算符能夠用來判斷某個構造函數的 prototype 屬性是否存在另一個要檢測對象的原型鏈上。

function Rectangle(length, width){
  this.length = length;
  this.width = width
}

Rectangle.prototype.getArea = function(){
  return this.length * this.width
}

Rectangle.prototype.toString = function(){
  return "[Rectangle " + this.length + "x" + this.width + "]";
}

// inherits from Rectangle
function Square(size){
  this.length = size;
  this.width = size;
}

Square.prototype = new Rectangle(); // 儘管是 Square.prototype是指向了 Rectangle的對象實例,即Square的實例對象也能訪問該實例的屬性(若是你提早聲明瞭該對象,且給該對象新增屬性)。
// Square.prototype = Rectangle.prototype; // 這種實現沒有上面這種好,由於Square.prototype 指向了 Rectangle.prototype,致使修改Square.prototype時,實際就是修改Rectangle.prototype。
console.log(Square.prototype.constructor); // 輸出 Rectangle 構造函數

Square.prototype.constructor = Square; // 重置回 Square 構造函數
console.log(Square.prototype.constructor); // 輸出 Square 構造函數

Square.prototype.toString = function(){
  return "[Square " + this.length + "x" + this.width + "]";
}

var rect = new Rectangle(5, 10);
var square = new Square(6);

console.log(rect.getArea()); // 50
console.log(square.getArea()); // 36

console.log(rect.toString()); // "[Rectangle 5 * 10]", 但若是是Square.prototype = Rectangle.prototype,則這裏會"[Square 5 * 10]"
console.log(square.toString()); // "[Square 6 * 6]"

console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
console.log(square instanceof Object); // true
複製代碼

Square.prototype 並不真的須要被改爲爲一個 Rectangle 對象。事實上,是 Square.prototype 須要指向 Rectangle.prototype 使得繼承得以實現。這意味着能夠用 Object.create() 簡化例子。

// inherits from Rectangle
function Square(size){
  this.length = size;
  this.width = size;
}

Square.prototype= Object.create(Rectangle.prototype, {
  constructor: {
    configurable: true,
    enumerable: true,
    value: Square,
    writable: true
  }
})
複製代碼

在對原型對象添加屬性前要確保你已經改爲了原型對象,不然在改寫時會丟失以前添加的方法(由於繼承是將被繼承對象賦值給須要繼承的原型對象,至關於重寫了須要繼承的原型對象)。

5.4 構造函數竊取

因爲 JavaScript 中的繼承是經過原型對象鏈來實現的,所以不須要調用對象的父類的構造函數。若是確實須要在子類構造函數中調用父類構造函數,那就能夠在子類的構造函數中利用 callapply 方法調用父類的構造函數。

// 在上面的代碼基礎上做出修改
// inherits from Rectangle
function Square(size){
  Rectangle.call(this, size, size);

  // optional: add new properties or override existing ones here
}
複製代碼

通常來講,須要修改 prototype 來繼承方法並用構造函數竊取來設置屬性,因爲這種作法模仿了那些基於類的語言的類繼承,因此這一般被稱爲僞類繼承。

5.5 訪問父類方法

其實也是經過指定 callapply 的子對象調用父類方法。

相關文章
相關標籤/搜索