web前端進階之js設(shè)計模式篇——上

設(shè)計模式,從設(shè)計到模式

  • 設(shè)計:設(shè)計原則(統(tǒng)一指導思想)
  • 模式:通過概念總結(jié)出的一些模板,可以效仿的固定式的東西(根據(jù)指導思想結(jié)合開發(fā)經(jīng)驗,總結(jié)出固定的樣式或模板)

按類型分

創(chuàng)建型(對象的創(chuàng)建及生成)
image.png
組合型(對象和類是怎樣的組合形式,一個類不一定能滿足需求,通過組合的形式完成)
image.png
行為型(涵蓋了開發(fā)中的一些常用的行為,如何設(shè)計才能滿足需求)
image.png

image.png

工廠模式(實例化對象模式)

image.png

image.png
  • demo
//Creator是個工廠,有個create函數(shù),工廠通過create創(chuàng)建Product
    class Product {
        constructor(name) {
            this.name = name
        }
        init() {
            alert(`${this.name}`)
        }
        fn1() {
            alert('this is fn1')
        }
        fn2() {
            alert('this is fn2')
        }
    }

    class Creator {
        create(name) {
            // 生成類的一個實例
            return new Product(name)
        }
    }
    // 測試
    // 生成一個工廠
    let creator = new Creator()
    // 通過工廠省城product的實例
    let p = creator.create('p')
    p.init()
    p.fn1()
    let p1 = creator.create('p1')
    p1.init()


    // 通過工廠模式將函數(shù)封裝好,暴露一個接口即可
class jQuery {
    constructor(selector) {
        // 獲取數(shù)組的slice
        let slice = Array.prototype.slice
        // 獲取節(jié)點,利用slice.call將其結(jié)果返回給一個數(shù)組,因為可能是多個dom節(jié)點
        let dom = slice.call(document.querySelectorAll(selector))
        // 獲取dom的長度
        let len = dom ? dom.length : 0
        // 進行循環(huán)
        for (let i = 0; i < len; i++) {
            // 將dom的數(shù)組元素賦值給this也就是實例的元素,元素的k就是數(shù)組的k,0,1,2...
            this[i] = dom[i]
        }
        // 賦值數(shù)組的長度
        this.length = len
        this.selector = selector || ''
    }
    append(node) {
        //...
    }
    addClass(name) {
        //...
    }
    html(data) {
        //...
    }
    // 此處省略若干 API
}

// 這個函數(shù)相當于工廠,封裝了返回實例的操作
// 入口,這個$是個函數(shù),函數(shù)里面返回一個jquery實例
window.$ = function(selector) {
   
    return new jQuery(selector)
}

console.log($(p))

題外話:讀源碼lib的意義
1、學習如何實現(xiàn)功能
2、學習設(shè)計思路
3、強制模擬好的代碼

  • 設(shè)計原則驗證
    1、構(gòu)造函數(shù)和創(chuàng)建者分離
    2、符合開放封閉原則

單例模式

介紹
  • 系統(tǒng)中被唯一使用
  • 一個類只有一個實例(在內(nèi)部使用實例,不可以在外部使用)
舉例
  • 登錄框
  • 購物車
說明
  • js中沒有private這個私有屬性關(guān)鍵字,typescript除外
java代碼實例
image.png

image.png
js代碼實例
// js中使用單例模式
    class SingleObject {
        // 在這里面定義的方法非靜態(tài),初始化實例時,都會有l(wèi)ogin()這個方法
        login() {
            console.log('login...')
        }

    }
    // 定義一個靜態(tài)的方法,將方法掛載到class上面,無論SingleObject被new多少個,getInstance的方法只有一個
    SingleObject.getInstance = (function() {
        let instance
        return function() {
            // 如果沒有則賦值,初始化
            if (!instance) {
                instance = new SingleObject();
            }
            // 有的話直接返回
            return instance
        }
    })()

    // js中的單例模式只能靠文檔去約束,不能使用private關(guān)鍵字去約束
    // 測試:注意這里只能使用靜態(tài)函數(shù)getInstance,不能使用new SingleObject()
    let obj1 = SingleObject.getInstance()
    obj1.login()
    let obj2 = SingleObject.getInstance()
    obj2.login()

    // 單例模式(唯一的),每次獲取的都是一個東西,所以他兩三等,否則就不是單例模式
    console.log(obj1 === obj2) //true
  • jquery中的$是單例模式
// 不管用幾個jquery文件,他自字內(nèi)部會處理,引用同一個$
    // 單例模式的思想,如果有直接用,沒有的話,實例化一個
    if(window.jQuery != null){
        return window.jQuery
    }else{
        // init
    }
  • 模擬登錄框
 class LoginForm {
        constructor() {
            // 初始化屬性,默認是隱藏的
            this.state = "hide"
        }
        show() {
            // 當前是show,alert已經(jīng)顯示
            if (this.state == "show") {
                alert('已經(jīng)顯示')
                // return 不在執(zhí)行了
                return
            }
            // 如果不等于show,就繼續(xù)執(zhí)行,讓他等于show
            this.state = "show"
            alert('登錄框顯示成功')
        }
        hide() {
            if (this.state = "hide") {
                alert('已經(jīng)隱藏')
                return
            }
            this.state = "hide"
            alert('登錄框隱藏')
        }
    }
    // 自定義函數(shù)在這里定義的目的,就是為了加一個閉包的變量instace,防止變量污染
    LoginForm.getInstance = (function() {
        let instace
        // 這里函數(shù)目的在于獲取單例的變量
        return function() {
            if (!instace) {
                instace = new LoginForm()
            }
            return instace
        }

    })()

    // 測試

    // 模擬第一個頁面的登錄框讓其顯示
    let login1 = LoginForm.getInstance()
    login1.show()

    // 模擬第二個頁面的登錄框讓其隱藏
    let login2 = LoginForm.getInstance()
    login2.show()
  • vue中的store也是單例模式
  • 設(shè)計原則驗證
    1、符合單一職責原則,只實例化唯一的對象
    2、沒法具體體現(xiàn)開放封閉原則,但是絕不違反開放封閉原則

適配器模式

介紹
  • 舊接口格式和使用者不兼容
  • 中間加一個適配轉(zhuǎn)換接口
js代碼演示
    // 舊接口
        class Adaptee{
            specificRequest(){
                return '這是舊接口'
            }
        }
        // 新接口
        class Target{
            constructor(){
                this.adaptee = new Adaptee()
            }
            request(){
                // 進行轉(zhuǎn)換
                let info = this.adaptee.specificRequest()
                return `${info}--新接口`

            }
        }
        // 測試
        let target = new Target()
        console.log(target.request())
  • 封裝舊接口(舊接口和新接口起沖突時,使用適配器修改)


    image.png

    image.png
  • vue中的computed就是適配模式
  • 設(shè)計原則驗證
    1、將舊接口和使用者進行分離(所有分離和解耦的方式都屬于開放封閉原則)
    2、符合開放封閉原則

裝飾器模式(既能使用原有的功能,又能使用裝飾后的功能)

介紹
  • 為對象添加新功能
  • 不改變其原有的結(jié)構(gòu)和功能
舉例
  • 比如手機可以打電話、發(fā)短信,我們在原有的基礎(chǔ)上裝個保護殼,防止摔落時損壞
js代碼演示
    class Circle {
        draw() {
            console.log('我要畫一個圓')
        }
    }

    class Decorator {
        constructor(circle) {
            this.circle = circle
        }
        draw() {
            this.circle.draw()
            this.setBorder(circle)
        }
        setBorder(circle) {
            console.log('我還畫了條線')
        }
    }
    // 測試

    // 想引用某個類的時候?qū)⑺麑嵗詤?shù)的形式進行傳遞進去
    let circle = new Circle()
    circle.draw()
    console.log('+++++')
    let dec = new Decorator(circle)
    dec.draw()
ES7裝飾器

考慮到瀏覽器兼容問題,需要安裝插件支持
1、npm install babel-plugin-transform-decorators-legacy --save-dev
2、對babel進行配置


image.png
  • 裝飾類
// 1、首先定義一個類
// 2、定義一個函數(shù),傳個target參數(shù)
// 3、在Demo這個類上面,加個@testDemo(這個就是裝飾器),通過@語法將這個類裝飾一遍,target這個參數(shù)其實就是Demo這個類

@testDemo
class Demo{

}

function testDemo(target){
    target.isDec = true
}

alert(Demo.isDec)
// +++++++++++++++++++++++++++++++
// 裝飾器原理

/*@decorator
class A {}*/

// 等同于

class A {}

// 將A定義成decorator函數(shù)執(zhí)行一遍的返回值(相當于A在decorator執(zhí)行了一遍),沒有的話返回A

A = decorator(A) || A

// +++++++++++++++++++++++++++++++

// 可以加個參數(shù)
@testDemo1(false)
class Demo1{

}


function testDemo1(isDec){
    // 這里面返回一個函數(shù),裝飾器返回的都是一個函數(shù)
    return function(target){
        target.isDec = isDec
    }
}
alert(Demo1.isDec)
  • 裝飾類—mixinshi示例

// 先定義一個mixins,獲取解構(gòu)的集合...list
function mixins(...list) {
    // return一個函數(shù),裝飾器都是一個函數(shù)
  return function (target) {
    // 將target.prototype和...list集合 混合在一起
    Object.assign(target.prototype, ...list)
  }
}
// 混合的對象
const Foo = {
  foo() { alert('foo') }
}

// 加個mixins的裝飾器,將Foo對象傳遞進去
// @mixins(Foo) 一執(zhí)行,最后返回的是個函數(shù),通過@關(guān)鍵字使用裝飾器,將Foo里面的屬性和并到target.prototype上
@mixins(Foo)
class MyClass {}

 // MyClass()里面沒有Foo,通過裝飾器將他們合并便具有了foo()方法
let obj = new MyClass();
obj.foo() // 'foo'
  • 裝飾方法—示例1
// 所有裝飾器都是一個函數(shù),最后將其返回
/*
@param target——Person這個類
@param name——當前屬性點
@param descriptor——屬性描述
*/
function readonly(target, name, descriptor){
  // descriptor對象原來的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  // 將可寫的關(guān)閉
  descriptor.writable = false;
  // 將其返回,所有被@readonly裝飾過的只可讀不可寫
  return descriptor;
}

class Person {
    constructor() {
        this.first = 'A'
        this.last = 'B'
    }

    @readonly
    // name()是Person里面的一個方法,我們想要只能獲取不能修改,所以加個@readonly裝飾器
    name() { return `${this.first} ${this.last}` }
}

var p = new Person()
console.log(p.name())
p.name = function () {} // 這里會報錯,因為 name 加了裝飾器,就是只讀屬性

  • 裝飾方法—示例2
function log(target, name, descriptor) {
    
  // 獲取value,其實就是add函數(shù)
  var oldValue = descriptor.value;
// 將value重新賦值一個函數(shù)
  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    // 將原本的函數(shù)執(zhí)行一下也就是add(),apply改變this的指向
    return oldValue.apply(this, arguments);
  };

  return descriptor;
}
// 定義一個Math類,希望在執(zhí)行add執(zhí)行的時候打印一個日志
class Math {
  // 被裝飾過的add()就是一個行的value,新的value就是先打印日志,再去做之前加法的執(zhí)行,最后將其返回
  @log
  add(a, b) {
    return a + b;
  }
}

const math = new Math();
// 執(zhí)行 add 時,會自動打印日志,因為有@log裝飾器
const result = math.add(2, 4);
console.log('result', result);
  • 第三方庫core-decorators(提供常用的裝飾器)
    1、安裝npm i core-decorators --save
    2、直接引用,就不需要自己寫了
import {readonly} from "core-decorators"

 class Person {
    @readonly
    name(){
        return 'this is test'
    }
 }
let p = new Person()

// 可讀不可寫
 alert(p.name())  

// 此裝飾器提醒用戶方法已廢棄
import { deprecate } from 'core-decorators';

class Person {
  @deprecate('這個里面可以自己添加提示語')
  facepalm() {}

  @deprecate('We stopped facepalming')
  facepalmHard() {}

  @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming

person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
//     See http://knowyourmeme.com/memes/facepalm for more details.
  • 設(shè)計原則驗證
    1、將現(xiàn)有對象和裝飾器進行分離,兩者獨立存在(解耦)
    2、符合開放封閉原則

代理模式

介紹
  • 使用者無權(quán)訪問目標對象
  • 中間加代理,通過代理做授權(quán)和控制
js代碼演示
class ReadImg{
    constructor(filename){
        this.filename = filename
        this.loadImg()
    }
    display(){
        console.log('display---'+ this.filename)
    }
    loadImg(){
        console.log('loading---'+this.filename)
    }

}


class ProxyImg{
    constructor(filename){
        this.readImg = new ReadImg(filename)
    }
    display(){
        this.readImg.display()
    }
}

let proxyImg = new ProxyImg('1.png')

proxyImg.display()
使用場景

1、網(wǎng)頁事件代理

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style type="text/css">
        *{
            padding: 0;
            margin: 0;
        }
        ul li{
            list-style: none;
        }
    </style>
</head>
<body>
<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
</body>
<script type="text/javascript">
    let ul = document.getElementById('ul')
    ul.addEventListener('click',function(e){
        if(e.target.nodeName === "LI"){
            alert(e.target.innerHTML)
        }
    })
</script>
</html>

2、this的指向

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style type="text/css">
        *{
            padding: 0;
            margin: 0;
        }
        ul li{
            list-style: none;
        }
    </style>
</head>
<body>
<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
</body>
<script type="text/javascript">
    let ul = document.getElementById('ul')
    ul.addEventListener('click',function(){
        var _this = this;
    // 箭頭函數(shù)里面this的執(zhí)行是window,所以利用代理的方式將其值賦給_this
        setTimeout(function(){
            _this.style.backgroundColor = 'red'
        },2000)
    })
</script>
</html>

3、$.proxy


image.png

4、ES6 Proxy

// 明星
let star = {
    name: '張XX',
    age: 25,
    phone: '13910733521'
}

// 經(jīng)紀人agent 
// star 代理的對象,監(jiān)聽代理的獲取 get 和設(shè)置 set 屬性
// 注意代理的接口要和原生的一樣 比如要知道name,就寫name
let agent = new Proxy(star, {
    // target代理對象, key就是代理對象的值
    get: function (target, key) {
        if (key === 'phone') {
            // 返回經(jīng)紀人自己的手機號
            return '18611112222'
        }

        if (key === 'price') {
            // 明星不報價,經(jīng)紀人報價
            return 120000
        }
        // 如果不是在這個兩種情況,直接返回target[key]
        return target[key]
    },
    
    set: function (target, key, val) {
        // 這是我們自己定義的價格
        if (key === 'customPrice') {
            if (val < 100000) {
                // 最低 10w,小于10萬,報錯
                throw new Error('價格太低')
            } else {
                target[key] = val
                // 這里寫renturn true 要不然不會賦值成功
                return true
            }
        }
    }
})

// 測試+++++++++++
// 主辦方
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)

// 想自己提供報價(砍價,或者高價爭搶)
agent.customPrice = 150000
// agent.customPrice = 90000  // 報錯:價格太低
console.log('customPrice', agent.customPrice)
  • 設(shè)計原則驗證
    1、代理類和目標類分離,隔離開目標類和使用者
    2、符合開放封閉原則

代理模式、適配模式、裝飾器模式三者的區(qū)別

  • 適配器:提供一個不同的接口(進行格式的轉(zhuǎn)換)
  • 代理模式:提供一模一樣的接口(無權(quán)使用主類,所以進行代理,提供一個一模一樣的接口)
  • 裝飾器模式:擴展功能,原有功能不變且可直接使用
  • 代理模式:顯示原有功能,但是經(jīng)過限制或閹割之后的

外觀模式

介紹
  • 為子系統(tǒng)中的一組接口提供了一個高層接口
  • 使用者使用了這個高層接口


    image.png
場景

傳多個參數(shù)都可適用


image.png
設(shè)計原則驗證

1、不符合單一職責和開放封閉原則,因此謹慎使用,不可濫用(不要為了設(shè)計而設(shè)計,而是為了使用而設(shè)計)

觀察者模式★

介紹
  • 發(fā)布 & 訂閱(定好東西,付了款,會有人上門送,比如訂牛奶、報紙啊等)
  • 一對多(N)(可以同時訂購牛奶,報紙,兩者之間沒什么沖突)
js代碼
// 主題,保存狀態(tài),接收狀態(tài)變化,狀態(tài)變化后觸發(fā)所有觀察者對象
class Subject {
    constructor() {
        // 狀態(tài)
        this.state = 0
        // 所有觀察者為一個數(shù)組
        this.observers = []
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
        this.notifyAllObservers()
    }
    // 添加一個新的觀察者
    attach(observer) {
        this.observers.push(observer)
    }
    // 循環(huán)所有的觀察者
    notifyAllObservers() {
        this.observers.forEach(observer => {
            // 遍歷的每個元素執(zhí)行update方法
            observer.update()
        })
    }
}

// 觀察者,等待被觸發(fā)
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)


場景

1、網(wǎng)頁事件綁定
所有的事件監(jiān)聽都是觀察者模式,所有事件寫好(訂閱)之后,等待被執(zhí)行


image.png

2、Promise中的then
在then里面寫好邏輯之后,不會馬上觸發(fā),等待Promise的狀態(tài)發(fā)生改變時觸發(fā)


image.png

image.png

3、jquery中的callbacks
這是個底層的api,比如使用ajax時,就會到這個api里面執(zhí)行


image.png

4、node.js自定義事件
  • event
// 基礎(chǔ)api的引用
const EventEmitter = require('events').EventEmitter

const emitter1 = new EventEmitter()
emitter1.on('some', info => {
    // 監(jiān)聽 some 事件
    console.log('f1',info)
})
emitter1.on('some', info => {
    // 監(jiān)聽 some 事件
    console.log('f2',info)
})
// 觸發(fā) some 事件
emitter1.emit('some','XXX')



// +++++++++++++++++++++++++++++++++++
const emitter = new EventEmitter()
emitter.on('sbowName', name => {
    console.log('event occured ', name)
})
// emit 時候可以傳遞參數(shù)過去
emitter.emit('sbowName', 'zhangsan')  


// +++++++++++++++++++++++++++++++++++
// 繼承
// 任何構(gòu)造函數(shù)都可以繼承 EventEmitter 的方法 on emit
class Dog extends EventEmitter {
    constructor(name) {
        super()
        this.name = name
    }
}
var simon = new Dog('simon')
// 因為 simon 繼承了 Dog ,所以便有他的 on 方法
simon.on('bark', function () {
    console.log(this.name, ' barked')
})
setInterval(() => {
    // 每一秒觸發(fā)bark,都回去執(zhí)行  console.log(this.name, ' barked')
    simon.emit('bark')
}, 500)

  • stream
// stream 用到自定義事件
const fs = require('fs')
// 讀取文件的 Stream
const readStream = fs.createReadStream('./data/file1.txt')  

// 此方法是看文件有多少字符
let length = 0
// 監(jiān)聽這個流
// 將數(shù)據(jù)處理的函數(shù)和結(jié)束的函數(shù)定義好,直接去執(zhí)行流,等待觸發(fā)即可
readStream.on('data', function (chunk) {
    // chunk讀一點,吐出一點
    length += chunk.toString().length
})
readStream.on('end', function () {
    console.log(length)
})


  • readline
var fs = require('fs')
var readline = require('readline');

// 查看有多少行
var rl = readline.createInterface({
    input: fs.createReadStream('./data/file1.txt')
});

var lineNum = 0
// 這里是監(jiān)聽一行一行的數(shù)據(jù)
rl.on('line', function(line){
    lineNum++
});
rl.on('close', function() {
    console.log('lineNum', lineNum)
});
  • 處理http請求
var http = require('http')

function serverCallback(req, res) {
    var method = req.method.toLowerCase() // 獲取請求的方法
    if (method === 'get') {
    }
    if (method === 'post') {
        // 接收 post 請求的內(nèi)容
        var data = ''
        req.on('data', function (chunk) {
            // “一點一點”接收內(nèi)容
            console.log('chunk', chunk.toString())
            data += chunk.toString()
        })
        req.on('end', function () {
            // 接收完畢,將內(nèi)容輸出
            console.log('end')
            res.writeHead(200, {'Content-type': 'text/html'})
            res.write(data)
            res.end()
        })
    }
    
}
http.createServer(serverCallback).listen(8081)  // 注意端口別和其他 server 的沖突
console.log('監(jiān)聽 8081 端口……')
  • vue和react組件生命周期觸發(fā)
    組件其實都是構(gòu)造函數(shù),生成一個組件相當于構(gòu)造函數(shù)初始化實例
  • vue watch


    image.png
設(shè)計原則驗證
  • 主題和觀察者分離,不是主動觸發(fā)而是被動監(jiān)聽,兩者解耦
  • 符合開放封閉原則

迭代器模式

介紹

1、順序訪問一個集合(有序列表,數(shù)組,對象是個無序列表)
2、使用者無需知道集合的內(nèi)部結(jié)構(gòu)(封裝,目的在于生成一個訪問機制,不需要外界知道內(nèi)部結(jié)構(gòu))

示例
  • 三個不同的數(shù)據(jù)結(jié)構(gòu)遍歷的方法有三種


    image.png
  • 統(tǒng)一的遍歷方式


    image.png
代碼演示
class Iterator {
    constructor(conatiner) {
        this.list = conatiner.list
        this.index = 0
    }
    next() {

        if (this.hasNext()) {
             // 如果還有下一項,直接返回當前這一項的index++
            return this.list[this.index++]
        }
        // 如果沒有,則返回null
        return null
    }
    hasNext() {
        // 判斷有沒有下一項 
        // this.index >= this.list.length 這句話的意思是有沒有到頭
        if (this.index >= this.list.length) {
            return false
        }
        // 如果沒有就是還有下一項
        return true
    }
}

class Container {
    constructor(list) {
        this.list = list
    }
    // 生成遍歷器
    getIterator() {
        // 遍歷器是有依據(jù)的,所以要傳遞一個參數(shù)
        return new Iterator(this)
    }
}

// 測試代碼
let container = new Container([1, 2, 3, 4, 5])
// 生成一個遍歷器,通過這個遍歷器可以兼容所有的有序結(jié)集合的數(shù)據(jù)結(jié)構(gòu)
let iterator = container.getIterator()
while(iterator.hasNext()) {
    console.log(iterator.next())
}

ES6 Iterator

  • ES6 Iterator為何存在


    image.png
  • ES6 Iterator是什么


    image.png
  • 示例

// 傳入的data可以是任意的
function each(data) {
    // 生成遍歷器,類似jquery生成的遍歷器
    let iterator = data[Symbol.iterator]()

    // 有數(shù)據(jù)時返回 {value: 1, done: false}
    // console.log(iterator.next())  
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next())
    // 沒有值的時候返回undefined,done 等于 true 就結(jié)束了
    // console.log(iterator.next())  // 沒有數(shù)據(jù)時返回 {value: undefined, done: true}
    // 因為不知道所遍歷數(shù)據(jù)的長度length,所以使用while循環(huán)
    let item = { done: false }
    while (!item.done) {
        // 每次獲取next
        item = iterator.next()
        // 判斷done是否結(jié)束
        if (!item.done) {
            console.log(item.value)
        }
    }
}


// 測試
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
// 如果是對象,可以利用Map.set
let m = new Map()
m.set('a', 100)
m.set('b', 200)

each(arr)
each(nodeList)
each(m)
image.png
設(shè)計原則驗證
  • 迭代器對象和目標對象分離
  • 迭代器將使用者與目標對象隔離開
  • 符合開放封閉原則

狀態(tài)模式

介紹

1、一個對象有狀態(tài)變化
2、每次狀態(tài)變化都會觸發(fā)一個邏輯
3、不能總是用if...else來控制

js代碼
// 把狀態(tài)抽象出來
// 狀態(tài)(紅綠燈)
class State {
    constructor(color) {
        this.color = color
    }
    handle(context) {
        console.log(`turn to ${this.color} light`)
        // 設(shè)置狀態(tài)
        context.setState(this)
    }
}
// 主體 實例
class Context {
    constructor() {
        this.state = null
    }
    // 獲取狀態(tài)
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
    }
    
}

// 測試代碼
let context = new Context()

let greed = new State('greed')
let yellow = new State('yellow')
let red = new State('red')

// 綠燈亮了
greed.handle(context)
console.log(context.getState())
// 黃燈亮了
yellow.handle(context)
console.log(context.getState())
// 紅燈亮了
red.handle(context)
console.log(context.getState())

場景

1、有限狀態(tài)機

  • 有限個狀態(tài)、以及在這些狀態(tài)之間的變化
  • 第三方庫,npm i javascript-state-machine --save
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <p>有限狀態(tài)機</p>
    <button id="btn"></button>
    
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="./03-javascript-state-machine.js"></script>
    <script>
        // 狀態(tài)機模型
        var fsm = new StateMachine({
            init: '收藏',  // 初始狀態(tài),待收藏
            transitions: [
                {
                    name: 'doStore', 
                    from: '收藏',
                    to: '取消收藏'
                },
                {
                    name: 'deleteStore',
                    from: '取消收藏',
                    to: '收藏'
                }
            ],
            methods: {
                // 監(jiān)聽執(zhí)行收藏
                onDoStore: function () {
                    alert('收藏成功') // 可以post請求
                    updateText()
                },
                // 取消收藏
                onDeleteStore: function () {
                    alert('已取消收藏')
                    updateText()
                }
            }
        })

        var $btn = $('#btn')

        // 點擊事件
        $btn.click(function () {
            if (fsm.is('收藏')) {
                fsm.doStore(1)
            } else {
                fsm.deleteStore()
            }
        })

        // 更新按鈕文案
        function updateText() {
            $btn.text(fsm.state)
        }

        // 初始化文案
        updateText()
    </script>
</body>
</html>

2、實現(xiàn)簡單的Promise

  • promise是個class
  • 在初始化的時候傳進去一個函數(shù),函數(shù)有2個參數(shù)resolve、reject
  • 要實現(xiàn)then的方法,then接收2個參數(shù)成功和失敗


    image.png
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="./03-javascript-state-machine.js"></script>
    <script>
    // 狀態(tài)機模型
    var fsm = new StateMachine({
        // 初始化狀態(tài)
        init: 'pending',
        // 變化
        transitions: [{
                // 事件名稱
                name: 'resolve',
                from: 'pending',
                to: 'fullfilled'
            },
            {
                // 事件名稱
                name: 'reject',
                from: 'pending',
                to: 'rejected'
            }
        ],
        methods: {
            // 監(jiān)聽resolve成功
            onResolve: function(state, data) {
                // 參數(shù):state - 當前狀態(tài)機實例; data - fsm.resolve(xxx) 執(zhí)行時傳遞過來的參數(shù)
                data.successList.forEach(fn => fn())
            },
            // 監(jiān)聽reject失敗
            onReject: function(state, data) {
                // 參數(shù):state - 當前狀態(tài)機實例; data - fsm.reject(xxx) 執(zhí)行時傳遞過來的參數(shù)
                data.failList.forEach(fn => fn())
            }
        }
    })

    // 定義 Promise
    class MyPromise {
        constructor(fn) {
            // 存儲起來
            this.successList = []
            this.failList = []

            fn(() => {
                // resolve 函數(shù) 我們定義的name
                fsm.resolve(this)
            }, () => {
                // reject 函數(shù)
                fsm.reject(this)
            })
        }
        // 實現(xiàn)then函數(shù)
        then(successFn, failFn) {
            this.successList.push(successFn)
            this.failList.push(failFn)
        }
    }

    // 測試代碼
    function loadImg(src) {
        // 初始化傳遞2個參數(shù)
        const promise = new MyPromise(function(resolve, reject) {
            var img = document.createElement('img')
            img.onload = function() {
                resolve(img)
            }
            img.onerror = function() {
                reject()
            }
            img.src = src
        })
        return promise
    }
    var src = 'http://www.imooc.com/static/img/index/logo_new.png'
    // result就是返回promise的實例
    var result = loadImg(src)
    console.log(result)
    // 此處沒有鏈式操作
    result.then(function(img) {
        console.log('success 1')
    }, function() {
        console.log('failed 1')
    })
    result.then(function(img) {
        console.log('success 2')
    }, function() {
        console.log('failed 2')
    })
    </script>
</body>

</html>
設(shè)計原則驗證
  • 將狀態(tài)對象和主題對象分離,狀態(tài)的變化邏輯單獨處理
  • 符合開放封閉原則

設(shè)計模式文檔

http://www.runoob.com/design-pattern/singleton-pattern.html

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

相關(guān)閱讀更多精彩內(nèi)容

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