【Dmitri Pavlutin】JavaScript繼承:理解構造函數屬性

翻譯:道奇
做者:Dmitri Pavlutin
原文:Inheritance in JavaScript: Understanding the constructor Propertyhtml

JavaScript有一種有趣的繼承機制:prototypal(原型鏈),大部分剛開始JavaScript的開發人員很難理解它,我也是。bash

JavaScript中的全部類型(除了nullundefined值)都有構造函數屬性,它是繼承的一部分。例如:函數

var num = 150;
num.constructor === Number // => true
var obj = {};
obj.constructor === Object // => true
var reg = /\d/g;
reg.constructor === RegExp; // => true
複製代碼

在這篇文章中,咱們將深刻學習對象的構造函數屬性,它做爲類的公共特性,意味着它能夠用於:學習

  • 標識出對象屬於哪一個類(instanceOf的另外選擇)
  • 從對象或原型(prototype)引用構造函數
  • 獲取類名

1.原始類型的構造函數

JavaScript中,原始類型指的是數字、布爾、字符串、symbol(ES6新增類型),nullundefined。除了nullundefined以外的任何值都有構造函數屬性,該屬性指的是對應的類型函數:ui

  • 數字的Number(): (1).constructor === Number
  • 布爾類型的Boolean(): (true).constructor === Boolean
  • 字符串的String(): ('hello').constructor === String
  • SymbolSymbol(): Symbol().constructor === Symbol

經過將它與相應的函數進行比較,可將原始類型的構造函數屬性用於肯定它的類型,例如,驗證值是不是數字:this

if (myVariable.constructor === Number) {
   // 當myVariable是數字時,代碼才執行
   myVariable += 1;
}
複製代碼

注意,這種方法通常不推薦,更推薦typeof的方式(見1.1),但這種方法在switch語句中頗有用,能夠減小if/else的數量:spa

// myVariable = ...
var type;
switch (myVariable.constructor) {
  case Number:
    type = 'number';
    break;
  case Boolean:
    type = 'boolean';
    break;
  case String:
    type = 'string';
    break;
  case Symbol:
    type = 'symbol';
    break;
  default:
    type = 'unknown';
    break;
}
複製代碼

1.1 原始值的包裝對象

經過new運算符調用函數時,會建立一個原始值的包裝對象,new String('str'),new Number(15)new Boolean(true)均可以建立一個包裝對象,但Symbol是不會建立包裝對象的,由於以new Symbol('symbol')這種方式調用會產生類型異常的錯誤。prototype

包裝對象容許開發人員將自定義屬性和方法綁定到原始值上,由於JavaScript不容許原始值有本身的屬性。翻譯

在基於構造函數判斷變量的類型時,這些包裝對象的存在可能會對形成理解上的混亂,由於包裝對象具備與原始值相同的構造函數:code

var booleanObject = new Boolean(false);
booleanObject.constructor === Boolean // => true

var booleanPrimitive = false;
booleanPrimitive.constructor === Boolean // => true
複製代碼

2.原型(prototype)對象的構造函數

原型(prototype)中的構造函數屬性會自動設置爲構造函數的引用。

function Cat(name) {
  this.name = name;
}
Cat.prototype.getName = function() {
  return this.name;
}
Cat.prototype.clone = function() {
  return new this.constructor(this.name);
}
Cat.prototype.constructor === Cat // => true
複製代碼

由於屬性是繼承自原型(prototype)的,實例對象也有構造函數。

var catInstance = new Cat('Mew');
catInstance.constructor === Cat // => true
複製代碼

甚至從直接量上建立的對象,也是繼承自Object.prototype

var simpleObject = {
  weekDay: 'Sunday'
};
simpleObject.prototype === Object // => true
複製代碼

2.1 不要在子類中丟失構造函數

構造函數是原型對象中常規的不可枚舉屬性,當基於它建立新的對象的時候,它不會自動更新。當建立子類時,須要手動設置正確的構造函數。

下面的例子爲超類Cat建立一個子類Tiger。注意初始化時Tiger.prototype仍然指向Cat構造函數。

function Tiger(name) {
   Cat.call(this, name);
}
Tiger.prototype = Object.create(Cat.prototype);
//prototype有個不正確的構造函數
Tiger.prototype.constructor === Cat   // => true
Tiger.prototype.constructor === Tiger // => false
複製代碼

如今若是使用Cat.prototype中定義的clone()方法克隆一個Tiger實例,會建立一個錯誤的Cat實例。

var tigerInstance = new Tiger('RrrMew');
var wrongTigerClone = tigerInstance.clone();
tigerInstance instanceof Tiger    // => true
// 注意wrongTigerClone是個不正確的Cat實例
wrongTigerClone instanceof Tiger  // => false
wrongTigerClone instanceof Cat    // => true
複製代碼

會出錯的緣由是Cat.prototype.clone()使用 new this.constructor()建立新的備份,但構造函數始終指向Cat函數。

爲了解決這個問題,必需使用正確的構造函數:Tiger,手動更新Tiger.prototype,這樣clone()方法也會被修復。

//修改Tiger原型構造函數
Tiger.prototype.constructor = Tiger;
Tiger.prototype.constructor === Tiger // => true

var tigerInstance = new Tiger('RrrMew');
var correctTigerClone = tigerInstance.clone();

//注意correctTigerClone是正確的Tiger實例
correctTigerClone instanceof Tiger  // => true
correctTigerClone instanceof Cat    // => false
複製代碼

查看此demo以得到完整的示例。

instanceof的另外一個選擇

object instanceof Class用於肯定對象是否和Class有一樣的prototype

這個操做符也會在原型鏈裏進行搜索,這樣作有時候會使得區分子類實例和超類實例變得很困難,例如:

var tigerInstance = new Tiger('RrrMew');

tigerInstance instanceof Cat   // => true
tigerInstance instanceof Tiger // => true
複製代碼

就像這個例子中看到的,不太可能準確的確認tigerInstanceCat仍是Tiger,由於instanceof在兩種狀況下都返回true。 這就是構造函數屬性的閃光點,它容許嚴格肯定實例類。

tigerInstance.constructor === Cat   // => false
tigerInstance.constructor === Tiger // => true

// 或者使用switch
var type;
switch (tigerInstance.constructor) {
  case Cat:
    type = 'Cat';
    break;
  case Tiger:
    type = 'Tiger';
    break;
  default:
    type = 'unknown';    
}
type // => 'Tiger'
複製代碼

獲取類名

JavaScript中的函數對象有個屬性名稱,它返回函數名或匿名函數的空字符串。

除了構造函數屬性以外,這對於肯定類名也頗有用,做爲Object.prototype.toString.call(objectInstance)的另一種選擇。

var reg = /\d+/;
reg.constructor.name                // => 'RegExp'
Object.prototype.toString.call(reg) // => '[object RegExp]'

var myCat = new Cat('Sweet');
myCat.constructor.name                // => 'Cat'
Object.prototype.toString.call(myCat) // => '[object Object]'
複製代碼

可是name返回匿名函數的空字符串(可是在ES6中,名稱能夠推斷出來),這種方法應該謹慎使用。

總結

構造函數屬性是JavaScript的繼承機制的一小部分。建立類的層級結構時應採起預防措施, 可是,它提供了肯定實例類型的很好的替代方法。

還能夠看一下
Object.prototype.constructor
What’s up with the constructor property in JavaScript?

相關文章
相關標籤/搜索