設(shè)計模式,從設(shè)計到模式
- 設(shè)計:設(shè)計原則(統(tǒng)一指導思想)
- 模式:通過概念總結(jié)出的一些模板,可以效仿的固定式的東西(根據(jù)指導思想結(jié)合開發(fā)經(jīng)驗,總結(jié)出固定的樣式或模板)
按類型分
創(chuàng)建型(對象的創(chuàng)建及生成)

組合型(對象和類是怎樣的組合形式,一個類不一定能滿足需求,通過組合的形式完成)

行為型(涵蓋了開發(fā)中的一些常用的行為,如何設(shè)計才能滿足需求)


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


- 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代碼實例


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

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ù)都可適用

設(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í)行

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


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

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)

設(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)的變化邏輯單獨處理
- 符合開放封閉原則









