淺談JS物件的建立、原型、原型鏈繼承

original_galaxy發表於2018-08-17

JS的內建物件,可分為兩類:

  • 資料封裝類物件:Object、Boolean、Number、String、Array
  • 其他物件:Function、Date、Error、Math、RegExp、Arguments

自然,除了內建物件外,可以通過JS程式碼自定義物件。

先總結下建立物件的方式,每種方式繼承的原型物件不同:

  • 建構函式:原型為建構函式的prototype屬性
  • Object.create():原型為傳入的第一個引數,若第一個引數為null,以Object.prototype為原型;第二個引數為可選的屬性描述符

下面分別看一下程式碼示例:

1. 建構函式方式

function Phone(brand) {
  this.brand = brand
}
let nokia = new Phone('Nokia')
console.log(nokia.constructor === Phone); // true
console.log(nokia.constructor.prototype === Phone.prototype); // true
console.log(Phone.prototype.__proto__ === Object.prototype); // true => the final relationship between Phone() and Object()
console.log('Object.prototype.__proto__: ', Object.prototype.__proto__); // null

function Smartphone(model) {
  this.model = model
}
let iphone8 = new Smartphone('iphone8')
console.log(iphone8.constructor === Smartphone); // true

// prototype繼承
Smartphone.prototype = new Phone('Huawei')
let honor = new Smartphone('honor')
console.log(honor.constructor === Smartphone); // false
console.log(honor.constructor === Phone); // true
console.log(honor.brand); // Huawei => 繼承了brand屬性
console.log(honor.model); // honor

// 直接以字面量形式宣告時,相當於建構函式為Object
let ironman = {name: 'Tony Stark'}
console.log(ironman.constructor === Object) // true
console.log(ironman.constructor.prototype === Object.prototype) // true
複製程式碼

2. 利用Object.create()

// 建立一個原型為null的物件
let spiderman = Object.create(null, {
  props: {
    name: 'Peter Park'
  }
});
console.log(spiderman.constructor); // => undefined
    
// 建立一個原型為Array的物件
let array = Object.create(Array.prototype, {});
console.log(array.constructor); // => Array()
console.log(array.constructor.prototype); // => [constructor: ƒ, ...]

// 建立一個原型為自定義類的物件
function Car() {
  this.fix = function() {
    console.log('going to fix');
  }
}
let audi = Object.create(Car.prototype, {});
audi.ov = '3.0T'
Car.prototype.charge = function() {
  console.log('going to charge');
}
console.log(audi.constructor); // Car()
console.log(audi.constructor.prototype); // {charge: ƒ, constructor: ƒ}
複製程式碼

js通過對原型的操作,可以模擬高階語言物件中的繼承特性:

  • prototype是函式層面的屬性,隱性原型屬性__proto__是物件(包括函式)層面的屬性。
  • 一個物件的__proto__,即是其構造器函式的prototype。
  • prototype作為一個物件,亦擁有__proto__屬性,指向的是更上一層構造器函式的prototype。
  • 直到__proto__指向的是最上層的Object.prototype,且到此後,Object.prototype.__proto__的值為null。
  • JS的新版本趨勢中,逐步開始模擬高階語言(特別是Java)的特性,描述例項成員、靜態成員、繼承等特性,越來接近物件導向程式設計。

進一步看下prototype繼承:

將某個類的prototype指向為某個物件後,此類將會繼承該物件的所有例項成員,靜態成員除外。看例子:

function Phone(brand) {
  this.brand = brand
  this.online = function() {
    console.log(brand + ' is going to online');
  }
}
let nokia = new Phone('Nokia')
console.log(nokia.__proto__ === nokia.constructor.prototype); // true => 物件的__proto__即是其構造器的prototype
console.log(nokia.constructor.prototype.__proto__ === Object.prototype); // true
console.log('prototype of nokia: ', nokia.prototype); // undefined => 可再次看出prototype是構造器層面的屬性,且__proto__是物件層面的屬性
nokia.online(); // Nokia is going to online
Phone.prototype.onMsg = function() {
  console.log('nokia message coming');
}
nokia.onMsg(); // nokia message coming

function Smartphone(model) {
  this.model = model
  this.oncalling = function() {
    console.log(model + ' is on calling');
  }
}
let iphone8 = new Smartphone('iphone8')
iphone8.oncalling(); // iphone8 is on calling

// 繼承方式之一:
// 此方式優缺點:簡單易於實現, 但若為子類新增屬性和方法,要在賦值了prototype之後執行, 無法實現多繼承
Smartphone.prototype = new Phone('Xiaomi') // 繼承Phone物件例項的所有成員,靜態成員除外
let iphoneX = new Smartphone('iphoneX')

console.log(iphoneX.brand); // Xiaomi
console.log(iphoneX.model); // iphoneX
iphoneX.online(); // Xiaomi is going to online
iphoneX.onMsg(); // nokia message coming
iphoneX.oncalling(); // iphoneX is on calling

console.log(iphoneX instanceof Phone); // true 
console.log(iphoneX instanceof Smartphone); // true
複製程式碼

順帶提一下call方法也可以實現繼承:

function Animal(name){   
  this.name = name;   
  this.showName = function(){   
    console.log(this.name);   
  }   
}   
function Cat(name){  
  Animal.call(this, name);  
}    
var cat = new Cat("Black Cat");   
cat.showName(); // Black Cat
複製程式碼

進一步看下原型鏈

淺談JS物件的建立、原型、原型鏈繼承

function Ball(name){
  this.name = name;
  this.showName = function () {
    console.log('I am ' + this.name + ' from this'); 
  }
}
Ball.prototype.showName = function () {
  console.log('I am ' + this.name + ' from prototype'); 
}
let football = new Ball("football");
football.fly = function () {
  console.log(this.name + ' is flying');
}
football.fly(); // football is flying

// 先從自身找,找不到在__proto__中去找,沒找到,去找上層__proto__,直到結果為null為止
football.showName(); // I am football from this
複製程式碼

相關文章