觀javascript(es6)設計模式系統講解與應用筆記

何爲設計
即按照哪種思路或者標準來實現功能
功能相同,能夠有不一樣的設計方案來實現
伴隨着需求的增長,設計的做用才能體現出來
設計準則:
1 小既是美
2 讓每一個程序只作好一件事
3 快速創建原型
4 捨棄高效率而取可移植性
5採用純文原本存儲數據
6 充分利用軟件的槓桿效應(軟件複用)
7使用shell腳原本提升槓桿效應和可移植性
8 避免強制性的用戶界面
9讓每個程序都稱爲過濾器
小準則:
容許用戶定製環境
儘可能使操做系統內核小而輕量化
使用小寫字母並儘可能簡短
沉默是金
各部分之和大於總體
尋求90%的解決方案css

SOLID五大設計原則
S-單一職責原則(一個程序只作好一件事。 若是功能過於複雜就拆開,每一個部分保持獨立)html

O-開放封閉原則(對擴展開放,對修改封閉 增長需求時,擴展新代碼,而非修改已有的代碼 這是軟件設計的終極目標)前端

L-李氏置換原則(子類能覆蓋父類 父類能出現的地方子類就能出現 js中使用較少(弱類型&繼承使用較少))vue

I-接口獨立原則(保持接口的單一獨立 避免出現胖接口 js中沒有接口(typescript例外)使用較少 相似於單一職責原則 這裏更關注接口)node

D-依賴致使原則(面向接口編程,依賴於抽象而不是依賴於具體 使用方只關注具體類的實現 js中使用較少(沒有接口&弱類型))jquery

設計原則總結
SO 前端體現較多
LID 體現較少git

用Promise來講明SO
單一職責原則: 每一個then中的邏輯只作好一件事
開放封閉原則: 若是新增長需求,擴展thenes6

function loadImg (src) {
        let promise = new Promise(function (resolve, reject) {
          let img = document.createElement('img')
          img.onload = function () {
            resolve(img)
          }
          img.onerror = function () {
            reject(new Error('圖片加載失敗'))
          }
          img.src = src
          this.$refs.div.appendChild(img)
        })
        return promise
      }
      let src = 'http://pic44.nipic.com/20140723/18505720_094503373000_2.jpg'
      let result = loadImg(src)
      result.then(function (img) {
        alert(`width:${img.width}`)
        return img
      }).then(function (img) {
        alert(`height:${img.height}`)
        return img
      }).then(function (img) {
        alert(`src:${img.src}`)
      }).catch(function (sx) {
        alert(sx)
      })

23種設計模式

建立型

**工廠模式(工廠方法模式,抽象工廠模式,建造者模式)
單例模式 原型模式**github

結構型

適配器模式 裝飾器模式 代理模式 外觀模式 橋接模式 組合模式 享元模式面試

行爲型

策略模式 迭代器模式 模板方法模式 職責鏈模式 觀察者模式 命令模式 備忘錄模式 中介者模式 狀態模式 解釋器模式 訪問者模式

刻意訓練

面試題
image.png
1 畫出UML類圖
image.png
2 用es6寫出來

class Car {
        constructor (name, number) {
          this.number = number
          this.name = name
        }
      }
      // 快車 子類
      class Kuaiche extends Car {
        constructor (name, number) {
          super(name, number)
          this.price = 1
        }
      }
      class Chuanche extends Car {
        constructor (name, number) {
          super(name, number)
          this.price = 2
        }
      }
      class Trip {
        constructor (car) {
          this.car = car
        }
        start () {
          console.log(`行程開始,名稱:${this.car.name},車牌號:${this.car.number}`)
        }
        end () {
          console.log('行程結束,金額:' + this.car.price * 5)
        }
      }
      let car = new Chuanche('桑塔納', 78652)
      let tatolTrip = new Trip(car)
      tatolTrip.start()
      tatolTrip.end()

第二題

image.png
UML類圖
image.png
代碼

// 車
class Car {
    constructor(num) {
        this.num = num
    }
}

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

// 出口顯示器
class Screen {
    show(car, inTime) {
        console.log('車牌號', car.num)
        console.log('停車時間', Date.now() - inTime)
    }
}

// 停車場
class Park {
    constructor(floors) {
        this.floors = floors || []
        this.camera = new Camera()
        this.screen = new Screen()
        this.carList = {}
    }
    in(car) {
        // 獲取攝像頭的信息:號碼 時間
        const info = this.camera.shot(car)
        // 停到某個車位
        const i = parseInt(Math.random() * 100 % 100)
        const place = this.floors[0].places[i]
        place.in()
        info.place = place
        // 記錄信息
        this.carList[car.num] = info
    }
    out(car) {
        // 獲取信息
        const info = this.carList[car.num]
        const place = info.place
        place.out()

        // 顯示時間
        this.screen.show(car, info.inTime)

        // 刪除信息存儲
        delete this.carList[car.num]
    }
    emptyNum() {
        return this.floors.map(floor => {
            return `${floor.index} 層還有 ${floor.emptyPlaceNum()} 個車位`
        }).join('\n')
    }
}

// 層
class Floor {
    constructor(index, places) {
        this.index = index
        this.places = places || []
    }
    emptyPlaceNum() {
        let num = 0
        this.places.forEach(p => {
            if (p.empty) {
                num = num + 1
            }
        })
        return num
    }
}

// 車位
class Place {
    constructor() {
        this.empty = true
    }
    in() {
        this.empty = false
    }
    out() {
        this.empty = true
    }
}

// 測試代碼------------------------------
// 初始化停車場
const floors = []
for (let i = 0; i < 3; i++) {
    const places = []
    for (let j = 0; j < 100; j++) {
        places[j] = new Place()
    }
    floors[i] = new Floor(i + 1, places)
}
const park = new Park(floors)

// 初始化車輛
const car1 = new Car('A1')
const car2 = new Car('A2')
const car3 = new Car('A3')

console.log('第一輛車進入')
console.log(park.emptyNum())
park.in(car1)
console.log('第二輛車進入')
console.log(park.emptyNum())
park.in(car2)
console.log('第一輛車離開')
park.out(car1)
console.log('第二輛車離開')
park.out(car2)

console.log('第三輛車進入')
console.log(park.emptyNum())
park.in(car3)
console.log('第三輛車離開')
park.out(car3)

image.png

工廠模式

class  Product {

constructor (name) {

this.name  =  name

}

init () {

alert('init')

}

fun1 () {

alert('fun1')

}

fun2 () {

alert('fun2')

}

}

class  Creator {

create (name) {

return  new  Product(name)

}

}

let  creator  =  new  Creator()

let  p  =  creator.create('p1')

p.init()

p.fun1()
模擬jQuery

`class jQuery {

constructor (selector) {

// 將函數的實際參數轉換成數組的方法

let slice = Array.prototype.slice

let dom = slice.call(document.querySelectorAll(selector))

let len = dom ? dom.length : 0

for (let i = 0; i < len; i++) {

this[i] = dom[i]

}

this.length = len

this.selector = selector || ''

}

append (node) {

}

addClass (name) {

}

html (data) {

}

}

window.$ = function (selector) {

return new jQuery(selector)

}

let di = window.$('div')

console.log(di)`

單例模式

class  SingleObject {

login () {

console.log('login....')

}

}

SingleObject.getInstance  = (function () { // SingleObject的靜態方法

let  instance

return  function () {

if (!instance) {

instance  =  new  SingleObject()

}

return  instance

}

})()

let  obj1  =  SingleObject.getInstance()

obj1.login()

let  obj2  =  SingleObject.getInstance()

obj2.login()

console.log('obj1 === obj2', obj1  ===  obj2)

console.log('分割線------------')

let  obj3  =  new  SingleObject()// 沒法徹底控制

console.log('obj1 === obj3', obj1  ===  obj3)

image.png

單例模式

符合單一職責的原則,只實例化惟一的對象

class  LoginForm() {

constructor() {

this.state  =  'hide'

}

hide() {

if(this.state  ===  'hide'){

console.log('已經隱藏')

return

}

this.state  ==  'hide'

consoel.log('隱藏成功')

}

show() {

if(this.state  ===  'show'){

console.log('已經顯示')

return

}

this.state  ===  'show'

console.log('顯示成功')

}

}

LoginForm.instance  = (function(){

let  instance

return  function(){

if(!instance){

instance  =  new  LoginForm()

}

return  instance

}

})()

let  login1  =  LoginForm.instance()

login1.hide()

let  login2  =  LoginForm.instance()

login2.hide()

適配模式

將舊接口與使用者進行分離

class  Adaptee {

specificRequest () {

return  '德國標準插頭'

}

}

class  Target {

constructor () {

this.Adaptee  =  new  Adaptee()

}

request () {

let  info  =  this.Adaptee.specificRequest()

return  `${info}-轉換器-中國的標準插頭`

}

}

let  target  =  new  Target()

let  res  =  target.request()

console.log(res)

vue中的computed方法也是適配模式

<template>

<div>

<p>{{msg}}</p>

<p>{{reverseMsg}}<p>

<div>

</template>

  

<script>

export  default {

  

data () {

return {

msg: 'hello'

}

},

computed: {

reverseMsg: function () {

return  this.msg.split('').reverse().join('')

}

}

}

<script>

image.png

裝飾器模式

爲對象添加新功能
不改變其原有的結構和功能
將現有對象和裝飾器進行分離,二者獨立存在

class  Circle {

draw () {

console.log('畫一個圓形')

}

}

class  Decorator {

constructor (circle) {

this.circle  =  circle

}

draw () {

this.circle.draw()

this.setRedBorder(circle)

}

setRedBorder (circle) {

console.log('設置紅色邊框')

}

}

// 測試代碼

let  circle  =  new  Circle()

circle.draw()

console.log('----------')

let  dec  =  new  Decorator(circle)

dec.draw()

image.png

裝飾類

至關於把類放到它頂上@的那個函數去執行,全部的裝飾器都是一個函數

function  testDec (dec) {

return  function (target) {

target.isDec  =  dec

}

}

@testDec(false)

class  Demo {

  

}

alert(Demo.isDec)


// 裝飾器的原理

@decorator

class  A {}

// 等同於

class  A {}

A  =  decorator(A)




function  mixins (...list) {

return  function (target) {

Object.assign(target.prototype, ...list)

}

}

const  Foo  = {

foo () {

alert('foo')

}

}

@mixins(Foo)

class  Demo {

  

}

let  demo  =  new  Demo()

demo.foo()

修飾方法

讓class類的方法只讀不可修改

// descriptor屬性描述對象(Object.defineProperty中會用到),原來的值以下:

// {

// value:specifiedFunction,//屬性的值

// enumerable:false,//是否可枚舉

// configurable:true,//是否可配置

// writable:true//是否可更改,寫入

// }
function  readonly (target, name, descriptor) {

descriptor.writable  =  false

return  descriptor

}

class  People {

constructor () {

this.first  =  'A'

this.last  =  'B'

}

@readonly

name () {

return  `${this.first} ${this.last}`

}

}

let  people  =  new  People()

console.log(people.name())

people.name  =  function () {

alert('a')

}

修飾添加的方法

**descriptor.value是要修飾的方法
name是這個方法名**

function  log (target, name, descriptor) {

let  oldValue  =  descriptor.value

descriptor.value  =  function () {

console.log(`calling ${name} width`, arguments)

return  oldValue.apply(this, arguments)

}

return  descriptor

}

class  Math {

@log

add (a, b) {

return  a  +  b

}

}

let  p  =  new  Math()

console.log(p.add(2, 4))

core-decorators.js

core-decorators.js是一個第三方模塊,提供了幾個常見的修飾器,經過它能夠更好地理解修飾器。

import {readonly} from  'core-decorators'
class  People {

@readonly

name () {

return  '小小'

}

}

let  p  =  new  People()

console.log(p.name())

p.name  =  function () {

alert('kkk')

}

image.png
表示該方法將要廢除

import {deprecate} from  'core-decorators'
class  People {

@deprecate('即將廢除', {url: 'http//www.baidu.com'})

name () {

return  '小小'

}

}

let  p  =  new  People()

console.log(p.name())

image.png

代理模式

**使用者無權訪問目標對象
中間加代理,經過代理作受權和控制**
代理類與目標類分離,隔離開目標類和使用者

class  ReadImg {

constructor (fileName) {

this.fileName  =  fileName

this.loadFromDisk()// 初始化即從硬盤中加載,模擬

}

display () {

console.log('display....'  +  this.fileName)

}

loadFromDisk () {

console.log('loading....'  +  this.fileName)

}

}

class  ProxyImg {

constructor (fileName) {

this.realImg  =  new  ReadImg(fileName)

}

display () {

this.realImg.display()

}

}

let  proxyImg  =  new  ProxyImg('png1')

proxyImg.display()

**場景:網頁事件代理

jQuery $.proxy
  Es6 Proxy**

網頁事件代理

<div  id="app">

<div  id="div1">

<a  href="#">a1<a>

<a  href="#">a2<a>

<a  href="#">a3<a>

<a  href="#">a4<a>

<a  href="#">a5<a

</div>

</div>

  

<!-- built files will be auto injected -->

<script  src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

<script>

var  div  =  document.getElementById('div1')

div.addEventListener('click',function(e){

let  target  =  e.target

if(target.nodeName  ==  'A'){

alert(target.innerHTMl)

}

})

<script>

jQuery的 $.proxy代理

<div  id="app">

<div  id="div1">

<a  href="#">a1<a>

<a  href="#">a2</a>

<a  href="#">a3</a>

<a  href="#">a4<a>

<a  href="#">a5</a>

</div>

</div>

  

<!-- built files will be auto injected -->

<script  src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

<script>

$('#div1').click(function(){

setTimeout($.proxy(function(){

$(this).css('background-color','yellow')

},this),1000)

})

<script>

Es6的proxy代理

let  star  = {

age: 25,

phone: 'star:177772177',

name: '小明'

}

let  agent  =  new  Proxy(star, {

get: function (target, key) { // target就是代理的對象,key是屬性

if (key  ===  'phone') {

return  'agent 188888888'

} else  if (key  ===  'price') {

return  12000

}

return  target[key]

},

set: function (target, key, val) { // val就是從新賦的值

if (key  ===  'customPrice') {

if (val  <  10000) {

throw  new  Error('過低了')

} else {

target[key] =  val

return  true

}

}

}

})

console.log(agent.phone)

console.log(agent.name)

console.log(agent.price)

agent.customPrice  =  1500

console.log('agent.customPrice', agent.customPrice)

image.png

代理模式VS適配器模式

適配器模式:提供一個不一樣的接口(如不一樣版本的插頭)
代理模式:提供如出一轍的接口

代理模式VS裝飾器模式

裝飾器模式:擴展功能,原有功能不變且可直接使用
代理模式:顯示原有的功能,可是通過限制或者閹割以後的

外觀模式

爲子系統中的一組接口提供了一個高層接口
使用者使用高層接口

不符合單一職責原則和開放封閉原則,所以謹慎使用,不可濫用
image.png

觀察者模式

**發佈 訂閱
一對多**
主題和觀察者分離,不是主動觸發而是被動監聽,二者解耦
image.png

// 主題,保存狀態,狀態改變以後觸發全部觀察者對象

class  Subject {

constructor () {

this.state  =  0

this.observers  = []

}

getState () {//獲取狀態

return  this.state

}

setState (state) {//設置狀態

this.state  =  state

this.notifyAllObservers()

}

notifyAllObservers () {//觸發全部的

this.observers.forEach(observer => {

observer.update()

})

}

attach (observer) {//將新的觀察者放入數組

this.observers.push(observer)

}

}

  

// 觀察者

class  Observer {

constructor (name, subject) {

this.name  =  name

this.subject  =  subject

this.subject.attach(this)//將當前的觀察者放入主題數組

}

update () {

console.log(`${this.name} update,state:${this.subject.getState()}`)

}

}

let  s  =  new  Subject()

let  o1  =  new  Observer('o1', s)

let  o2  =  new  Observer('o2', s)

let  o3  =  new  Observer('o3', s)

s.setState(1)

s.setState(2)

s.setState(3)

當我設置新值,會觸發全部觀察者的updata方法

網頁事件綁定

image.png

Promise

function  loadImg (src) {

var  promise  =  new  Promise(function (resolve, reject) {

var  img =  document.createElement('img')

document.body.appendChild(img)

img.onload =  function () {

resolve(img)

}

img.onerror  =  function () {

reject('圖片加載失敗')

}

img.src  =  src

})

return  promise

}

let  src  =  'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1577934260276&di=4733ee70834444d221274f497173c917&imgtype=0&src=http%3A%2F%2Fdmimg.5054399.com%2Fallimg%2Fpkm%2Fpk%2F22.jpg'

var  result  =  loadImg(src)

result.then(function (img) {

console.log('width', img.width)

return  img

}).then(function (img) {

console.log('height', img.height)

})

image.png

jQuery callbacks

image.png

Nodejs自定義事件

const  EventEmitter  =  require('events').EventEmitter

// 繼承

class  Dog  extends  EventEmitter{

constructor(name){

super()

this.name  =  name

}

}

let  simon  =  new  Dog('simon')

simon.on('bark',function(){

console.log(this,function(){

console.log(this.name,'barked')

})

})

setInterval(() => {

simon.emit('bark')

}, 1000)
const  fs  =  require('fs')

const  readStream  =  fs.createReadStream('./data/file1.txt')

let  length  =  0

readStream.on('data',function(chunk){

let  len  =  chunk.toString().length

console.log('len',len)

length  +=  len

})

readStream.on('end',function(){

console.log('length',length)

})
const  fs  =  require('fs')

const  readline  =  require('readline')

let  rl  =  readline.crateInterface({

input: fs.createReadStream('./data/file.txt')

})

let  lineNum  =  0;

rl.on('line',function(){

lineNum++

})

rl.on('close',function(){

console.log('lineNum',lineNum)

})

其餘場景

**nodejs中:處理http請求;多進程通信
Vue和React組件生命週期觸發
Vue watch**
image.png

image.png
image.png

迭代器模式

**順序訪問一個集合
使用者無需知道集合的內部結構(封裝)**
**迭代器對象與目標對象分離
迭代器將使用者與目標對象隔離開**

使用場景

好比說都是數組可是可使用的方法卻不一樣

<script  src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
let  arr  = [1, 2, 3]

let  nodeList  =  document.getElementsByName('a')

let  p  =  $('a')

arr.forEach(item => {

console.log(item)

});

var  i,len  =  nodeList.length

for(i=  0,i<len; i++){

console.log(nodeList[i])

}

p.each(function(key,elem){

console.log(key,elem)

})

寫一個函數封裝,迭代器

<script  src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

<script>

let  arr  = [1, 2, 3]

let  nodeList  =  document.getElementsByName('a')

let  p  =  $('a')

function  each(data){

let  $data  =  $(data)//生成迭代器 生成jquery的對象

$data.each(function(key,val){

console.log(key,val)

})

}

each(arr)

each(nodeList)

each(p)


迭代器模式的特色:
順序遍歷有序集合
使用者沒必要知道集合的內部結構

</script>

UML類圖
image.png

class  Interator {

constructor (container) {

this.list  =  container.list

this.index  =  0

}

next () {

if (this.hasNext()) {

return  this.list[this.index++]

}

return  null

}

hasNext () {

if (this.index  >=  this.list.length) {

return  false

}

return  true

}

}

class  Container {

constructor (list) {

this.list  =  list

}

getInterator () {

return  new  Interator(this)

}

}

let  arr  = [1, 2, 3, 4]

let  container  =  new  Container(arr)

let  interator  =  container.getInterator()

while (interator.hasNext()) {

console.log(interator.next())

}

image.png

**場景
jQuery each
ES6 Iterator**

ES6 Iterator爲什麼存在?

Es6語法中,有序集合的數據類型已經有不少
Array Map Set String TypedArray arguments NodeList
須要有一個統一的遍歷接口來遍歷全部數據類型
注意,object不是有序集合,能夠用Map代替

ES6 Interator是什麼?

**以上數據類型,都有【Symbol.interator】屬性/屬性key
屬性值是函數,執行函數返回一個迭代器**
**這個迭代器就有next方法可順序迭代子元素
可運行Array.prototype[Symbol.interator]來測試**

function  each (data) {

let  iterator  =  data[Symbol.iterator]()

console.log(iterator.next())

console.log(iterator.next())

console.log(iterator.next())

console.log(iterator.next())

}

let  arr  = [1, 2, 3]

let  nodeList  =  document.getElementsByTagName('p')

let  m  =  new  Map()

m.set('a', 100)

m.set('b', 100)

each(arr)

each(nodeList)

each(m)

image.png

Symbol.iterator並非人人都知道

也不是每一個人都須要封裝一個each方法

所以有了for..of語法 語法糖

優化

function  each (data) {

let  iterator  =  data[Symbol.iterator]()

let  item  = {done: false}

while (!item.done) {

item  =  iterator.next()

if (!item.done) {

console.log(item.value)

}

}

}

let  arr  = [1, 2, 3]

let  nodeList  =  document.getElementsByTagName('p')

let  m  =  new  Map()

m.set('a', 100)

m.set('b', 100)

each(arr)

each(nodeList)

each(m)

繼續優化

function  each (data) {

// 帶有遍歷器特性的對象:data[Symbol.iterator]有值

for (let  item  of  data) {

console.log(item)

}

}

let  arr  = [1, 2, 3]

let  nodeList  =  document.getElementsByTagName('p')

let  m  =  new  Map()

m.set('a', 100)

m.set('b', 100)

each(arr)

each(nodeList)

each(m)

image.png

ES6 Interator與Generator

Interator的價值不限於上述幾個類型的遍歷
還有generator函數的使用
即只要返回的數據符合Iterator接口的要求

便可使用Iterator語法,這就是迭代器模式
image.png

function*  foo () {

yield  '1'

yield  '2'

return  '6'

}

for (let  item  of  foo()) {

console.log(item)

}
相關文章
相關標籤/搜索