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ù)
- 在
Java中,封裝數(shù)據(jù)是由語(yǔ)法解析來(lái)實(shí)現(xiàn)的,提供了public、protected、private等關(guān)鍵字來(lái)提供不同的訪問(wèn)權(quán)限。 - 在
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)
- 封裝實(shí)現(xiàn)細(xì)節(jié)使對(duì)象內(nèi)部的變化對(duì)其他對(duì)象而言是透明的,即不可見(jiàn)的。
- 封裝實(shí)現(xiàn)細(xì)節(jié)使對(duì)象之間的耦合變松散,對(duì)象之間只通過(guò)暴露的
API接口來(lái)通訊。當(dāng)我們修改一個(gè)對(duì)象時(shí),可以隨意修改它的內(nèi)部實(shí)現(xiàn)。 - 封裝實(shí)現(xiàn)細(xì)節(jié)的例子有很多。例如:迭代器
each函數(shù)。
1.3 封裝類型
- 對(duì)于靜態(tài)類型語(yǔ)言,封裝類型是通過(guò)抽象類和接口來(lái)進(jìn)行的。將對(duì)象的真正類型隱藏在抽象類或者接口之后。
-
JavaScript是一門類型模糊語(yǔ)言。在封裝類型方面,JavaScript沒(méi)有能力,也沒(méi)有必要做得更多。 - 對(duì)于
JavaScript設(shè)計(jì)模式實(shí)現(xiàn)來(lái)說(shuō),不區(qū)分類型是一種失色,也可以說(shuō)是一種解脫。
1.4 封裝變化
- 考慮你的設(shè)計(jì)中哪些地方可能變化,找到并封裝,這是許多設(shè)計(jì)模式的主題。
- 設(shè)計(jì)模式被劃分為創(chuàng)建型模式、結(jié)構(gòu)型模式以及行為型模式。其中,創(chuàng)建型模式的目的就是封裝創(chuàng)建對(duì)象的變化,結(jié)構(gòu)型模式封裝的是對(duì)象之間的組合關(guān)系,行為型模式封裝的是對(duì)象的行為變化。
- 通過(guò)封裝變化的方式,把系統(tǒng)中穩(wěn)定不變的部分和容易變化的部分隔離開(kāi)來(lái),在系統(tǒng)的演變過(guò)程中,我們只需要替換那些容易變化的部分,如果這些部分是已經(jīng)封裝好的,替換起來(lái)也相對(duì)容易。這可以最大限度的保證程序的穩(wěn)定性和可擴(kuò)展性。
2 繼承
-
JavaScript選擇了基于原型的面向?qū)ο笙到y(tǒng)。在原型編程的思想中,類并不是必須的,對(duì)象未必從類中創(chuàng)建而來(lái),一個(gè)對(duì)象可以通過(guò)克隆另一個(gè)對(duì)象而得到。 - 雖然
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á)到繼承的效果。 - 原型繼承
var obj = {name: 'sven'}
var A = function() {}
A.prototype = obj
var a = new A()
console.log(a.name) //sven
name屬性查找:a → a.__proto__→ obj
- 原型繼承鏈
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)
- 多態(tài)將“做什么”和“誰(shuí)去做”分離開(kāi)來(lái)。實(shí)現(xiàn)多態(tài)的關(guān)鍵在于消除類型之間的耦合關(guān)系。
- 在
Java中,可以通過(guò)向上轉(zhuǎn)型來(lái)實(shí)現(xiàn)多態(tài)。由于JavaScript的變量類型在運(yùn)行期是可變的,所以JavaScript對(duì)象的多態(tài)性是與生俱來(lái)的。 - 多態(tài)的最根本好處在于,你不必再向?qū)ο笤儐?wèn)“你是什么類型”而后根據(jù)得到的答案調(diào)用對(duì)象的某個(gè)行為——你只管調(diào)用該行為就是了,其他的一切多態(tài)機(jī)制都會(huì)為你安排妥當(dāng)。
- 多態(tài)將過(guò)程化的條件分支語(yǔ)句轉(zhuǎn)化為對(duì)象的多態(tài)性,從而消除這些條件分支語(yǔ)句。
- 代碼演示
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類圖
-
類圖
類圖 -
類與類之間的關(guān)系
(1) 泛化表示繼承,用空心箭頭表示。
(2) 關(guān)聯(lián)表示引用,用實(shí)心箭頭表示。
類與類關(guān)系圖
5 案例
- 使用
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)
-
打車時(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è)屬性。
-
某停車廠分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è)類。
參考資料
- Javascript 設(shè)計(jì)模式系統(tǒng)講解與應(yīng)用
- 《JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》(曾探)



