[面試專題]JS設計模式

JS設計模式


發佈訂閱模式:

這種設計模式能夠大大下降程序模塊之間的耦合度,便於更加靈活的擴展和維護。html

// 一個播放器類
class Player {

  constructor() {
    // 初始化觀察者列表
    this.watchers = {}

    // 模擬2秒後發佈一個'play'事件
    setTimeout(() => {
      this._publish('play', true)
    }, 2000)

    // 模擬4秒後發佈一個'pause'事件
    setTimeout(() => {
      this._publish('pause', true)
    }, 4000)
  }

  // 發佈事件
  _publish(event, data) {
    if (this.watchers[event] && this.watchers[event].length) {
      this.watchers[event].forEach(callback => callback.bind(this)(data))
    }
  }

  // 訂閱事件
  subscribe(event, callback) {
    this.watchers[event] = this.watchers[event] || []
    this.watchers[event].push(callback)
  }

  // 退訂事件
  unsubscribe(event = null, callback = null) {
    // 若是傳入指定事件函數,則僅退訂此事件函數
    if (callback&&event) {
      if (this.watchers[event] && this.watchers[event].length) {
        this.watchers[event].splice(this.watchers[event].findIndex(cb => Object.is(cb, callback)), 1)
      }

    // 若是僅傳入事件名稱,則退訂此事件對應的全部的事件函數
    } else if (event) {
      this.watchers[event] = []

    // 若是未傳入任何參數,則退訂全部事件
    } else {
      this.watchers = {}
    }
  }
}

// 實例化播放器
const player = new Player()
console.log(player)

// 播放事件回調函數1
const onPlayerPlay1 = function(data) {
  console.log('1: Player is play, the `this` context is current player', this, data)
}

// 播放事件回調函數2
const onPlayerPlay2 = data => {
  console.log('2: Player is play', data)
}

// 暫停事件回調函數
const onPlayerPause = data => {
  console.log('Player is pause', data)
}

// 加載事件回調函數
const onPlayerLoaded = data => {
  console.log('Player is loaded', data)
}

// 可訂閱多個不一樣事件
player.subscribe('play', onPlayerPlay1)
player.subscribe('play', onPlayerPlay2)
player.subscribe('pause', onPlayerPause)
player.subscribe('loaded', onPlayerLoaded)

// 能夠退訂指定訂閱事件
player.unsubscribe('play', onPlayerPlay2)
// 退訂指定事件名稱下的全部訂閱事件
player.unsubscribe('play')
// 退訂全部訂閱事件
player.unsubscribe()

// 能夠在外部手動發出事件(真實生產場景中,發佈特性通常爲類內部私有方法)
player._publish('loaded', true)

中介者模式 Mediator Pattern:

觀察者模式經過維護一堆列表來管理對象間的多對多關係,中介者模式經過統一接口來維護一對多關係,且通訊者之間不須要知道彼此之間的關係,只須要約定好API便可。設計模式

// 汽車
class Bus {

  constructor() {

    // 初始化全部乘客
    this.passengers = {}
  }

  // 發佈廣播
  broadcast(passenger, message = passenger) {
    // 若是車上有乘客
    if (Object.keys(this.passengers).length) {

      // 若是是針對某個乘客發的,就單獨給他聽
      if (passenger.id && passenger.listen) {

        // 乘客他愛聽不聽
        if (this.passengers[passenger.id]) {
          this.passengers[passenger.id].listen(message)
        }

      // 否則就廣播給全部乘客
      } else {
        Object.keys(this.passengers).forEach(passenger => {
          if (this.passengers[passenger].listen) {
            this.passengers[passenger].listen(message)
          }
        })
      }
    }
  }

  // 乘客上車
  aboard(passenger) {
    this.passengers[passenger.id] = passenger
  }

  // 乘客下車
  debus(passenger) {
    this.passengers[passenger.id] = null
    delete this.passengers[passenger.id]
    console.log(`乘客${passenger.id}下車`)
  }

  // 開車
  start() {
    this.broadcast({ type: 1, content: '前方無障礙,開車!Over'})
  }

  // 停車
  end() {
    this.broadcast({ type: 2, content: '老司機翻車,停車!Over'})
  }
}

// 乘客
class Passenger {

  constructor(id) {
    this.id = id
  }

  // 聽廣播
  listen(message) {
    console.log(`乘客${this.id}收到消息`, message)
    // 乘客發現停車了,因而本身下車
    if (Object.is(message.type, 2)) {
      this.debus()
    }
  }

  // 下車
  debus() {
    console.log(`我是乘客${this.id},我如今要下車`, bus)
    bus.debus(this)
  }
}

// 建立一輛汽車
const bus = new Bus()

// 建立兩個乘客
const passenger1 = new Passenger(1)
const passenger2 = new Passenger(2)

// 倆乘客分別上車
bus.aboard(passenger1)
bus.aboard(passenger2)

// 2秒後開車
setTimeout(bus.start.bind(bus), 2000)

// 3秒時司機發現2號乘客沒買票,2號乘客被驅逐下車
setTimeout(() => {
  bus.broadcast(passenger2, { type: 3, content: '同志你好,你沒買票,請下車!' })
  bus.debus(passenger2)
}, 3000)

// 4秒後到站停車
setTimeout(bus.end.bind(bus), 3600)

// 6秒後再開車,車上已經沒乘客了
setTimeout(bus.start.bind(bus), 6666)

代理模式 Proxy Pattern:

爲其餘對象提供一種代理以控制對這個對象的訪問。
代理模式使得代理對象控制具體對象的引用。代理幾乎能夠是任何對象:文件,資源,內存中的對象,或者是一些難以複製的東西。閉包

ES6中的Proxy對象app

const target = {}
const handler = {
    get(target, property) {
        if (property in target) {
            return target[property]
        } else {
            throw new ReferenceError("Property \"" + property + "\" does not exist.")
        }
    }
}
const p = new Proxy(target, {})
p.a = 3  // 被轉發到代理的操做
console.log(p.c) //

單例模式 Singleton Pattern:

保證一個類只有一個實例,並提供一個訪問它的全局訪問點(調用一個類,任什麼時候候返回的都是同一個實例)。函數

實現方法:使用一個變量來標誌當前是否已經爲某個類建立過對象,若是建立了,則在下一次獲取該類的實例時,直接返回以前建立的對象,不然就建立一個對象。測試

// 類數實例:
class Singleton {
  constructor(name) {
    this.name = name
    this.instance = null   // 
  }
  getName() {
    alert(this.name)
  }
  static getInstance(name) {
    if (!this.instance) {
      this.instance = new Singleton(name)
    }
    return this.instance
  }
}
const ins = new Singleton('hhhh')
const instanceA = Singleton.getInstance('seven1')
const instanceB = Singleton.getInstance('seven2')
//閉包包裝實例:
const SingletonP = (function() {
  let instance
  return class Singleton {

    constructor(name) {
      if (instance) {
        return instance
      } else {
        this.init(name)
        instance = this
        return this
      }
    }

    init(name) {
      this.name = name
      console.log('已初始化')
    }
  }
})()

const instanceA = new SingletonP('seven1')
const instanceB = new SingletonP('seven2')
// ES5 iife
var SingletonTester = (function () {
    function Singleton(args) {
        var args = args || {};
        //設置name參數
        this.name = 'SingletonTester';
    }
    //實例容器
    var instance;
    return {
        name: 'SingletonTester',
        getInstance: function (args) {
            if (instance === undefined) {
                instance = new Singleton(args);
            }
            return instance;
        }
    };
})();

var singletonTest = SingletonTester.getInstance({ pointX: 5 });
console.log(singletonTest.pointX); // 輸出 5 
// 構造函數的屬性
function Universe() {
    if (typeof Universe.instance === 'object') {
        return Universe.instance;
    }
    this.start_time = 0;
    this.bang = "Big";
    Universe.instance = this;
}
// 測試
var uni = new Universe();
var uni2 = new Universe();
console.log(uni === uni2); // true
// 重寫構造函數
function Universe() {
    var instance = this;
    // 其它內容
    this.start_time = 0;
    this.bang = "Big";
    // 重寫構造函數
    Universe = function () {
        return instance;
    };
}
// 測試
var uni = new Universe();
var uni2 = new Universe();
uni.bang = "123";
console.log(uni === uni2); // true
console.log(uni2.bang); // 123

工廠模式 Factory Pattern:

工廠模式定義一個用於建立對象的接口,這個接口由子類決定實例化哪個類。該模式使一個類的實例化延遲到了子類。而子類能夠重寫接口方法以便建立的時候指定本身的對象類型。this

簡單說:假如咱們想在網頁面裏插入一些元素,而這些元素類型不固定,多是圖片、連接、文本,根據工廠模式的定義,在工廠模式下,工廠函數只需接受咱們要建立的元素的類型,其餘的工廠函數幫咱們處理。url

// 文本工廠
class Text {
    constructor(text) {
        this.text = text
    }
    insert(where) {
        const txt = document.createTextNode(this.text)
        where.appendChild(txt)
    }
}

// 連接工廠
class Link {
    constructor(url) {
        this.url = url
    }
    insert(where) {
        const link = document.createElement('a')
        link.href = this.url
        link.appendChild(document.createTextNode(this.url))
        where.appendChild(link)
    }
}

// 圖片工廠
class Image {
    constructor(url) {
        this.url = url
    }
    insert(where) {
        const img = document.createElement('img')
        img.src = this.url
        where.appendChild(img)
    }
}

// DOM工廠
class DomFactory {

  constructor(type) {
    return new (this[type]())
  }

  // 各流水線
  link() { return Link }
  text() { return Text }
  image() { return Image }
}

// 建立工廠
const linkFactory = new DomFactory('link')
const textFactory = new DomFactory('text')

linkFactory.url = 'https://surmon.me'
linkFactory.insert(document.body)

textFactory.text = 'HI! I am surmon.'
textFactory.insert(document.body)

裝飾者模式 Decorative Pattern:

裝飾者(decorator)模式可以在不改變對象自身的基礎上,在程序運行期間給對像動態的添加職責(方法或屬性)。與繼承相比,裝飾者是一種更輕便靈活的作法。設計

簡單說:能夠動態的給某個對象添加額外的職責,而不會影響從這個類中派生的其它對象。代理

ES7裝飾器
function isAnimal(target) {
    target.isAnimal = true
    return target
}

// 裝飾器
@isAnimal
class Cat {
    // ...
}
console.log(Cat.isAnimal)    // true



做用於類屬性的裝飾器:

function readonly(target, name, descriptor) {
    discriptor.writable = false
    return discriptor
}

class Cat {
    @readonly
    say() {
        console.log("meow ~")
    }
}

var kitty = new Cat()
kitty.say = function() {
    console.log("woof !")
}
kitty.say()    // meow ~

參考:
輸入理解js系列
來自ES6入門實踐

相關文章
相關標籤/搜索