JavaScript面向?qū)ο?/h2>

JavaScript沒(méi)有提供傳統(tǒng)面向?qū)ο笳Z(yǔ)言中的類式繼承,而是通過(guò)原型委托的方式來(lái)實(shí)現(xiàn)對(duì)象與對(duì)象之間的繼承。JavaScript也沒(méi)有在語(yǔ)言層面提供對(duì)抽象類和接口的支持。

1 封裝

封裝的目的在于將信息隱藏。廣義的封裝不僅包括封裝數(shù)據(jù)封裝實(shí)現(xiàn),還包括封裝類型封裝變化。

1.1 封裝數(shù)據(jù)

  1. Java中,封裝數(shù)據(jù)是由語(yǔ)法解析來(lái)實(shí)現(xiàn)的,提供了publicprotected、private等關(guān)鍵字來(lái)提供不同的訪問(wèn)權(quán)限。
  2. JavaScript中,只能依賴變量作用域來(lái)模擬實(shí)現(xiàn)封裝性。
var myObj = (function() {
  var _name = 'sven'
  return {
    getName: function() {
      return _name
    }
  }
})()

console.log(myObj.getName()) // sven
console.log(myObj._name) // undefined

1.2 封裝實(shí)現(xiàn)

  1. 封裝實(shí)現(xiàn)細(xì)節(jié)使對(duì)象內(nèi)部的變化對(duì)其他對(duì)象而言是透明的,即不可見(jiàn)的。
  2. 封裝實(shí)現(xiàn)細(xì)節(jié)使對(duì)象之間的耦合變松散,對(duì)象之間只通過(guò)暴露的API接口來(lái)通訊。當(dāng)我們修改一個(gè)對(duì)象時(shí),可以隨意修改它的內(nèi)部實(shí)現(xiàn)。
  3. 封裝實(shí)現(xiàn)細(xì)節(jié)的例子有很多。例如:迭代器each函數(shù)。

1.3 封裝類型

  1. 對(duì)于靜態(tài)類型語(yǔ)言,封裝類型是通過(guò)抽象類和接口來(lái)進(jìn)行的。將對(duì)象的真正類型隱藏在抽象類或者接口之后。
  2. JavaScript是一門類型模糊語(yǔ)言。在封裝類型方面,JavaScript沒(méi)有能力,也沒(méi)有必要做得更多。
  3. 對(duì)于JavaScript設(shè)計(jì)模式實(shí)現(xiàn)來(lái)說(shuō),不區(qū)分類型是一種失色,也可以說(shuō)是一種解脫。

1.4 封裝變化

  1. 考慮你的設(shè)計(jì)中哪些地方可能變化,找到并封裝,這是許多設(shè)計(jì)模式的主題。
  2. 設(shè)計(jì)模式被劃分為創(chuàng)建型模式、結(jié)構(gòu)型模式以及行為型模式。其中,創(chuàng)建型模式的目的就是封裝創(chuàng)建對(duì)象的變化,結(jié)構(gòu)型模式封裝的是對(duì)象之間的組合關(guān)系,行為型模式封裝的是對(duì)象的行為變化。
  3. 通過(guò)封裝變化的方式,把系統(tǒng)中穩(wěn)定不變的部分和容易變化的部分隔離開(kāi)來(lái),在系統(tǒng)的演變過(guò)程中,我們只需要替換那些容易變化的部分,如果這些部分是已經(jīng)封裝好的,替換起來(lái)也相對(duì)容易。這可以最大限度的保證程序的穩(wěn)定性和可擴(kuò)展性。

2 繼承

  1. JavaScript選擇了基于原型的面向?qū)ο笙到y(tǒng)。在原型編程的思想中,類并不是必須的,對(duì)象未必從類中創(chuàng)建而來(lái),一個(gè)對(duì)象可以通過(guò)克隆另一個(gè)對(duì)象而得到。
  2. 雖然JavaScript的對(duì)象最初都是由Object.prototype對(duì)象克隆而來(lái)的,但對(duì)象構(gòu)造器的原型可以動(dòng)態(tài)指向其它對(duì)象。這樣一來(lái),當(dāng)對(duì)象a需要借用對(duì)象b的能力時(shí),可以有選擇性地把對(duì)象a的構(gòu)造器的原型指向?qū)ο?code>b,從來(lái)達(dá)到繼承的效果。
  3. 原型繼承
var obj = {name: 'sven'}
var A = function() {}

A.prototype = obj

var a = new A()
console.log(a.name) //sven

name屬性查找:a → a.__proto__→ obj

  1. 原型繼承鏈
var obj = {name: 'sven'}

var A = function() {}
A.prototype = obj

var B = function() {}
B.prototype = new A()

var b = new B()
console.log(b.name) //sven

name屬性查找鏈:b → b.__proto__ → new A() → A.prototype → obj

3 多態(tài)

  1. 多態(tài)將“做什么”和“誰(shuí)去做”分離開(kāi)來(lái)。實(shí)現(xiàn)多態(tài)的關(guān)鍵在于消除類型之間的耦合關(guān)系。
  2. Java中,可以通過(guò)向上轉(zhuǎn)型來(lái)實(shí)現(xiàn)多態(tài)。由于JavaScript的變量類型在運(yùn)行期是可變的,所以JavaScript對(duì)象的多態(tài)性是與生俱來(lái)的。
  3. 多態(tài)的最根本好處在于,你不必再向?qū)ο笤儐?wèn)“你是什么類型”而后根據(jù)得到的答案調(diào)用對(duì)象的某個(gè)行為——你只管調(diào)用該行為就是了,其他的一切多態(tài)機(jī)制都會(huì)為你安排妥當(dāng)。
  4. 多態(tài)將過(guò)程化的條件分支語(yǔ)句轉(zhuǎn)化為對(duì)象的多態(tài)性,從而消除這些條件分支語(yǔ)句。
  5. 代碼演示
class GoogleMap {
  show() {
    console.log('開(kāi)始渲染谷歌地圖')
  }
}

class BaiduMap {
  show() {
    console.log('開(kāi)始渲染百度地圖')
  }
}

const renderMap = map => {
  if(map.show instanceof Function) {
    map.show()
  }
}

renderMap(new GoogleMap())
renderMap(new BaiduMap())

4 UML類圖

  1. 類圖


    類圖
  2. 類與類之間的關(guān)系
    (1) 泛化表示繼承,用空心箭頭表示。
    (2) 關(guān)聯(lián)表示引用,用實(shí)心箭頭表示。


    類與類關(guān)系圖

5 案例

  1. 使用class簡(jiǎn)單實(shí)現(xiàn)jQuery中的$選擇器
class jQuery {
  constructor(selector) {
    const slice = Array.prototype.slice
    const dom = slice.call(document.querySelectorAll(selector))
    this.selector = selector || ''
    const len = dom ? dom.length : 0;
    this.length = len
    for(let i = 0; i < len; i++) {
      this[i] = dom[i]
    }
  }
  append(node) {}
  addClass(name) {}
  html(data) {}
}

window.$ = selector => new jQuery(selector)
  1. 打車時(shí)可以打?qū)\嚮蚩燔?。任何車都有車牌?hào)和名稱。快車每公里1元,專車每公里2元。行程開(kāi)始時(shí),顯示車輛信息。行程結(jié)束時(shí),顯示打車金額。行程距離為5公里。


    UML類圖
//父類 - 車
class Car {
  constructor(name, number) {
    this.name = name
    this.number = number
  }
}

//子類 - 快車
class KuaiChe extends Car {
  constructor(name, number) {
    super(name, number)
    this.price = 1
  }
}

//子類 - 專車
class ZhuanChe extends Car {
  constructor(name, number) {
    super(name, number)
    this.price = 2
  }
}

//行程
class Trip {
  constructor(car, distance) {
    this.car = car
    this.distance = distance
  }

  start() {
    console.log(`行程開(kāi)始 車名為${this.car.name},車牌號(hào)為${this.car.number}`)
  }

  end() {
    console.log(`行程結(jié)束 車費(fèi)為${this.distance * this.car.price}`)
  }
}

const zhuanChe = new ZhuanChe('專車', '299567')
const trip = new Trip(zhuanChe, 5)
trip.start()
trip.end()

注意:將行程抽象為一個(gè)類,而不是車的一個(gè)屬性。

  1. 某停車廠分3層,每層100個(gè)車位。每個(gè)車位都能夠檢測(cè)到車輛的駛?cè)牒碗x開(kāi)。車輛進(jìn)入前,顯示每層空余車位數(shù)量。車輛進(jìn)入時(shí),攝像頭可識(shí)別車牌號(hào)和時(shí)間。車輛出來(lái)時(shí),出口顯示器顯示車牌號(hào)和停車時(shí)間。
    類: 停車場(chǎng)、層、車位、車輛、攝像頭、顯示器。


    UML類圖
//車輛
class Car {
  constructor(number) {
    this.number = number
  }
}

//停車位
class Stall {
  constructor() {
    this.empty = true
  }

  in() {
    this.empty = false
  }

  out() {
    this.empty = true
  }
}

//停車層
class Floor {
  constructor(index, stalls) {
    this.index = index
    this.stalls = stalls || []
  }

  emptyNum() {
    return this.stalls.filter(stall => stall.empty).length
  }
}

//出口顯示屏
class Screen {
  show(car, inTime) {
    console.log(`車牌號(hào)為${car.number},停留時(shí)間為${Date.now() - inTime}`)
  }
} 

//入口攝像頭
class Camera {
  shoot(car) {
    return {
      number: car.number,
      inTime: Date.now()
    }
  }
}

//停車場(chǎng)
class Park {
  constructor(floors, camera, screen) {
    this.camera = camera
    this.screen = screen
    this.floors = floors || []
    this.carList = {};
  }

  emptyNum() {
    let num = 0
    this.floors.forEach(floor => {
      const emptyNum = floor.emptyNum()
      num += emptyNum
    })
    return num;
  }

  showMsg() {
    let info = ''
    for(let i = 1; i < this.floors.length; i++) {
      const floor = this.floors[i]
      info += `第${floor.index}層還有${floor.emptyNum()}個(gè)空位`
    }
    console.log(info)
  }

  in(car) {
    if(this.emptyNum() > 0) {
      const info = this.camera.shoot(car)
      for(let i = 1; i < this.floors.length; i ++) {
        const floor = this.floors[i]
        const allNum = floor.stalls.length
        const emptyNum = floor.emptyNum()
        if(emptyNum > 0) {
          let index = 1; 
          while(!floor.stalls[index].empty) {
            index++
          }
          const stall = floor.stalls[index]
          stall.in()
          //保存停車位信息
          info.stall = stall
          break
        }
      } 
      this.carList[car.number] = info
    } else {
      console.log('停車場(chǎng)已滿')
    }
  }

  out(car) {
    const info = this.carList[car.number]
    info.stall.out()
    this.screen.show(car, info.inTime)
    delete this.carList[car.number]
  }
}

//測(cè)試代碼
const floors = []
for(let i = 1; i <= 3; i ++) {
  const stalls = []
  for(let j = 1; j <= 100; j++) {
    stalls[j] = new Stall()
  }
  floors[i] = new Floor(i, stalls)
}

const camera = new Camera()
const screen = new Screen()
const park = new Park(floors, camera, screen)
const car1 = new Car('100')
const car2 = new Car('200')
const car3 = new Car('300')
park.in(car1)
park.showMsg()
park.in(car2)
park.showMsg()
park.out(car1)
park.showMsg()
park.in(car3)
park.showMsg()
park.out(car2)
park.showMsg()
park.out(car3)
park.showMsg()

注意:① 引用指的是一個(gè)類持有另一個(gè)類,而不是一個(gè)類的方法以另一個(gè)類為參數(shù)。 ② 類與類之間應(yīng)該盡量減少耦合(最少知識(shí)原則),能夠通過(guò)將另一個(gè)類作為參數(shù)實(shí)現(xiàn),就不要持有另一個(gè)類。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容