JS多型封裝繼承
前言
js是一種基於物件的語言,在js中幾乎所有的東西都可以看成是一個物件,但是JS中的物件模型和大多數面嚮物件語言的物件模型不太一樣,因此理解JS中面向物件思想十分重要,接下來本篇文章將從多型、封裝、繼承三個基本特徵來理解JS的面向物件思想
多型
含義
同一操作作用於不同的物件上面,可以產生不同的解釋和不同的執行結果,也就是說,給不同的物件傳送同一個訊息時,這些物件會根據這個訊息分別給出不同的反饋。
舉個例子:假設家裡養了一隻貓和一隻狗,兩隻寵物都要吃飯,但是吃的東西不太一樣,根據主人的吃飯命令,貓要吃魚,狗要吃肉,這就包含了多型的思想在裡面,用JS程式碼來看就是:
let petEat = function (pet) {
pet.eat()
}
let Dog = function () {}
Dog.prototype.eat = function () {
console.log('吃肉')
}
let Cat = function () {}
Cat.prototype.eat = function () {
console.log('吃魚')
}
petEat(new Dog())
petEat(new Cat())
上面這段程式碼展示的就是物件的多型性,由於JS是一門動態型別語言,變數型別在執行時是可變的,因此一個JS物件既可以是Dog型別的物件也可以是Cat型別的物件,JS物件多型性是與生俱來的,而在靜態型別語言中,編譯時會進行型別匹配檢查,如果想要一個物件既表示Dog型別又表示Cat型別在編譯的時候就會報錯,當然也會有解決辦法,一般會通過繼承來實現向上轉型,這裡感興趣的可以去對比一下靜態語言的物件多型性。
作用
多型的作用是通過把過程化的條件分支語句轉化為物件的多型性,從而消除這些條件分支語句,舉個例子:還是上面寵物吃飯的問題,如果在沒有使用物件的多型性之前程式碼可能是這樣是的:
let petEat = function (pet) {
if (pet instanceof Dog) {
console.log('吃肉')
} else if (pet instanceof Cat) {
console.log('吃魚')
}
}
let Dog = function () {}
let Cat = function () {}
petEat(new Dog())
petEat(new Cat())
通過條件語句來判斷寵物的型別決定吃什麼,當家裡再養金魚,就需要再加一個條件分支,隨著新增的寵物越來越多,條件語句的分支就會越來越多,按照上面多型的寫法,就只需要新增物件和方法就行,解決了條件分支語句的問題
封裝
封裝的目的是將資訊隱藏,一般來說封裝包括封裝資料、封裝實現,接下來就逐一來看:
封裝資料
由於JS的變數定義沒有private、protected、public等關鍵字來提供許可權訪問,因此只能依賴作用域來實現封裝特性,來看例子
var package = (function () {
var inner = 'test'
return {
getInner: function () {
return inner
}
}
})()
console.log(package.getInner()) // test
console.log(package.inner) // undefined
封裝實現
封裝實現即隱藏實現細節、設計細節,封裝使得物件內部的變化對其他物件而言是不可見的,物件對它自己的行為負責,其他物件或者使用者都不關心它的內部實現,封裝使得物件之間的耦合變鬆散,物件之間只通過暴露的API介面來通訊。
封裝實現最常見的就是jQuery、Zepto、Lodash這類JS封裝庫中,使用者在使用的時候並不關心其內部實現,只要它們提供了正確的功能即可
廣州品牌設計公司https://www.houdianzi.com PPT模板下載大全https://redbox.wode007.com
繼承
繼承指的是可以讓某個型別的物件獲得另一個型別的物件的屬性和方法,JS中實現繼承的方式有多種,接下來就看看JS實現繼承的方式
建構函式繫結
這種實現繼承的方式很簡單,就是使用call或者apply方法將父物件的建構函式繫結在子物件上,舉個例子:
function Pet (name) {
this.type = '寵物'
this.getName = function () {
console.log(name)
}
}
function Cat (name) {
Pet.call(this, name)
this.name = name
}
let cat = new Cat('毛球')
console.log(cat.type) // 寵物
cat.getName() // 毛球
通過呼叫父建構函式的call方法實現了繼承,但是這種實現有一個問題,父類的方法是定義在建構函式內部的,對子類是不可見的
原型繼承
原型繼承的本質就是找到一個物件作為原型並克隆它。這句話怎麼理解,舉個例子:
function Pet (name) {
this.name = name
}
Pet.prototype.getName = function () {
return this.name
}
let p = new Pet('毛球')
console.log(p.name) // 毛球
console.log(p.getName()) // 毛球
console.log(Object.getPrototypeOf(p) === Pet.prototype) // true
上面這段程式碼中p物件實際上就是通過Pet.prototype的克隆和一些額外操作得來的,有了上面的程式碼基礎,接下來來看一個簡單的原型繼承程式碼:
let pet = {name: '毛球'}
let Cat = function () {}
Cat.prototype = pet
let c = new Cat()
console.log(c.name) // 毛球
來分析一下這段引擎做了哪幾件事:
- 首先遍歷c中的所有屬性,但是沒有找到name屬性
- 查詢name屬性的請求被委託給物件c的構造器原型即Cat.prototype,Cat.prototype是指向pet的
- 在pet物件中找到name屬性,並返回它的值
上面的程式碼實現原型繼承看起來有點繞,實際上在es5提供了Obejct.create()方法來實現原型繼承,舉個例子:
function Pet (name) {
this.name = name
}
Pet.prototype.getName = function () {
return this.name
}
let c = Object.create(new Pet('毛球'))
console.log(c.name) // 毛球
console.log(c.getName()) // 毛球
組合繼承
組合繼承即使用原型鏈實現對原型屬性和方法的繼承,通過建構函式實現對例項屬性的繼承,舉個例子:
function Pet (name) {
this.name = name
}
Pet.prototype.getName = function () {
return this.name
}
function Cat (name) {
Pet.call(this, name)
}
Cat.prototype = new Pet()
let c = new Cat('毛球')
console.log(c.name) // 毛球
console.log(c.getName()) // 毛球