重學JS: 多態封裝繼承

前言

JS是一種基於對象的語言,在JS中幾乎全部的東西均可以當作是一個對象,可是JS中的對象模型和大多數面嚮對象語言的對象模型不太同樣,所以理解JS中面向對象思想十分重要,接下來本篇文章將從多態、封裝、繼承三個基本特徵來理解JS的面向對象思想編程

多態

含義

同一操做做用於不一樣的對象上面,能夠產生不一樣的解釋和不一樣的執行結果,也就是說,給不一樣的對象發送同一個消息時,這些對象會根據這個消息分別給出不一樣的反饋。
舉個例子:假設家裏養了一隻貓和一隻狗,兩隻寵物都要吃飯,可是吃的東西不太同樣,根據主人的吃飯命令,貓要吃魚,狗要吃肉,這就包含了多態的思想在裏面,用JS代碼來看就是:app

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())
複製代碼

經過條件語句來判斷寵物的類型決定吃什麼,當家裏再養金魚,就須要再加一個條件分支,隨着新增的寵物愈來愈多,條件語句的分支就會愈來愈多,按照上面多態的寫法,就只須要新增對象和方法就行,解決了條件分支語句的問題ui

封裝

封裝的目的是將信息隱藏,通常來講封裝包括封裝數據、封裝實現,接下來就逐一來看:this

封裝數據

因爲JS的變量定義沒有private、protected、public等關鍵字來提供權限訪問,所以只能依賴做用域來實現封裝特性,來看例子es5

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封裝庫中,用戶在使用的時候並不關心其內部實現,只要它們提供了正確的功能便可spa

繼承

繼承指的是可讓某個類型的對象得到另外一個類型的對象的屬性和方法,JS中實現繼承的方式有多種,接下來就看看JS實現繼承的方式prototype

構造函數綁定

這種實現繼承的方式很簡單,就是使用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()) // 毛球
複製代碼

總結

本篇文章主要介紹了JS面向對象編程思想的多態封裝繼承的特性,這裏只作了淺析,想要深挖還須要更深刻的學習,但願看完本篇文章對理解JS面向對象編程思想有所幫助 若是有錯誤或不嚴謹的地方,歡迎批評指正,若是喜歡,歡迎點贊

相關文章
相關標籤/搜索