Javascript 中實現物件原型繼承的三種方式

allenWang發表於2018-03-02

Javacript 中有一個思想:萬物皆物件,幾個基礎型別(String, Number, Boolean, Null, Undefined, Symbol),幾個引用型別(Array, Date, Function, Object, Regexp)本質上都是物件。

那麼物件之間如何實現關聯(也就是OO裡面的繼承)呢?js中通過prototype(原型)來實現。每個物件都有一個__proto__指標,指向上一個原型,這就像是一個連結串列。最頂端的object的原型指向null, 代表終結。 所以,當我們定義一個變數:

let name = new String('allen')
//let name = 'allen'
console.log('name:', name)
console.log('name.__proto__:', name.__proto__)
console.log('name.__proto__.__proto__:',name.__proto__.__proto__)	
console.log('name.__proto__.__proto__.__proto__:', name.__proto__.__proto__.__proto__)
複製程式碼

輸出:

name: [String: 'allen']
name.__proto__: [String: '']
name.__proto__.__proto__: {}
name.__proto__.__proto__.__proto__: null
[Finished in 0.1s]
複製程式碼

我們要實現繼承(至少看上去是繼承的樣子)有三種方法:

  1. Object.create(),
  2. Function.prototype={},
  3. class extend

1. Object.create()

看例子:

let dog = {
	name: 'dog'
	}
	
let mardDog = Object.create(dog)
複製程式碼

這裡 mardDog 是一個新的空物件,存有一個指標__proto__,指向的是 dog 物件, dog物件中也有一個__proto__,指向的是ObjectObject中也有一個__proto__,指向的是null。 這就是原型鏈。利用Object.create(),我們可以建立多個物件,dog物件都是他們的原型,那麼他們可不可以改變dog裡面的屬性呢。答案是可以 的,但不建議。程式碼如下:

mardDog.name = 'mardDog'

/*
dog ====> {name: 'dog'}
mardDog ====> {name: 'mardDog'}
*/
複製程式碼

可以看到,我們並沒有能改變原型中的屬性。而下面這種方式:

mardDog.__proto__.name = 'xxxDog'

/*
	dog ====> {name: 'xxxDog'}
	mardDog ====> {name: 'mardDog'}
*/
複製程式碼

我們成功改變了上一層的屬性。 那麼,為什麼我們不推薦使用__proto__去改變原型中的共有屬性呢?因為這種方法非常慢,並且會嚴重影響程式。事實上,__proto__從來沒有被寫進規範,但是瀏覽器廠商都實現了它。

2. Function.prototype = {}

這個方法其實是利用建構函式來實現 先看例子:

function dog (){
	this.name = 'dog'
	this.age = 1
}

let dog1 = new dog()

function mardDog (){
	this.yiel = function (){
		console.log(this.name)
	}
}

mardDog.prototype = new dog()

let mardDog1 = new mardDog()
mardDog1.yiel()
複製程式碼

輸出:

dog
[Finished in 0.1s]
複製程式碼

可以看到,我們成功實現了mardDog 對 dog的繼承

3. class extend

es6中實現了 class 這個關鍵字,雖然只是語法糖,本質上是方法二的封裝,但這種思路對熟悉OO的開發者是很友好的,並且把js中令人迷惑的原型封裝了起來,使它變得更容易開發。

例子:

class dog {
   constructor () {
   	this.name = 'dog'
   	this.age = 1
   }
}

class mardDog extends dog {
	constructor () {
		super()
	}
	yiel () {
		console.log(this.name)
	}
}
let mardDog1 = new mardDog()
mardDog1.yiel()
複製程式碼

輸出:

dog
複製程式碼

這樣我們成功地實現了繼承

總結

由於es6 是大勢所趨,建議在工程環境中使用clas來實現物件的繼承。當然原型以及原型鏈的原理是必須掌握的。es6中還有一些方法也是十分有用。以下:

Object.getPrototypeOf(childobj) 顧名思義,該方法得到childobj的prototype,也可以理解為父類。


Object.setPrototypeOf(childObj, obj)這個方法是將childObj設為obj的繼承類。由於前面提到過,對prototype的操作十分微妙,所以這個方法還是能不用就不用,可以用Object.create(obj)來代替


其他方法參照MDN上的解釋

相關文章