js设计模式笔记小结

JS 设计模式

面向对象

搭建开发环境

npm / webapck / webpack-dev-server / babel
babel-core babel-loader babel-polyfill babel-preset-env

什么是面向对象

概念
类(对象的模板)、对象(实例)

class People {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
  speak() {
    console.log(`My name is ${this.name}, age ${this.age}`)
  }
}

let zhang = new People('zhang', 20)
zhang.eat()
zhang.speak()

let wang = new People('wang', 21)
wang.eat()
wang.speak()

三要素:继承封装多态

  • 继承:子类继承父类
    • 继承是父类,公共的,不仅仅服务于 Student
    • 继承可将公共方法抽离出来,提高复用,减少冗余
  • 封装:数据的权限和保密
    • public 完全开放
    • protected 对子类开放
    • private 对自己开放
    • ES6不支持,可以用 typescript 演示
    • 减少耦合,不该外露的不外露
    • 利于数据、接口权限的管理
    • ES6 目前不支持,一般认为_开头的属性是私有的
  • 多态:同一接口不同实现
    • JS 应用少
      • 保持子类的开放性和灵活性
      • 面向接口编程
// 继承
class People {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
  speak() {
    console.log(`My name is ${this.name}, age ${this.age}`)
  }
}

class Student extends People {
  constructor(name, age, number) {
    super(name, age)
    this.number = number
  }
  study() {
    console.log(`${this.name} study`)
  }
}

let xiaoming = new Student('xiaoming', 10, 'A1')
xiaoming.study()
console.log(xiaoming.number)
let xiaohong = new Student('xiaohong', 11, 'A2')
xiaohong.study()


// 多态
class People {
  constructor(name) {
    this.name = name
  }
  saySomething() {

  }
}
class A extends People {
  constructor(name) {
    super(name)
  }
  saySomething() {
    console.log('I am A')
  }
}
class B extends People {
  constructor(name) {
    super(name)
  }
  saySomething() {
    console.log('I am B')
  }
}
let a = new A('a')
a.saySomething()
let b = new B('b')
b.saySomething()

JS 应用举例

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) {

    }
    // 此处省略若干 API
}
window.$ = function(selector) {
  return new jQuery(selector)
}

面向对象的意义

  • 程序执行:顺序、判断、循环——结构化
  • 面向对象:数据结构化
  • 对于计算机,结构化的才是最简单的
  • 编程应该 简单 & 抽象

UML类图

统一建模语言。

  • 类图:UML 包含很多种图,本次相关的是类图
  • 关系:主要讲解泛化和关联
  • 泛化表示继承,关联表示引用
    类图:

关系

设计原则

  • 什么是设计
    • 即按照哪一种思路或者标准来实现功能
    • 功能相同,可以有不同设计方案来实现
    • 伴随着需求增加,设计的作用才能体现出来
  • UNIX / LINUX 设计哲学
    • 准则1:小即是美
    • 准则2:让每个程序只做好一件事
    • 准则3:快速建立原型
    • 准则4:舍弃高效率而取可移植性
    • 准则5:采用纯文本来存储数据
    • 准则6:软件复用
    • 准则7:使用 shell 脚本来提高杠杆效应和可移植性
    • 准则8:避免强制性的用户界面
    • 准则9:让每个程序都称为过滤器
    • 小准则:允许用户定制环境
    • 尽量使操作系统内核小而轻量化
    • 使用小写并尽量简短
    • 沉默是金
  • SOLID五大设计原则
    • S 单一职责
      一个程序只做好一件事
      如果功能过于复杂就拆分开,每个部分保持独立
    • O 开放封闭
      对扩展开放,对修改封闭
      增加需求,扩展新代码,而非修改已有代码
      这是软件设计的终极目标
    • L 李氏置换
      子类能覆盖父类
      父类能出现的地方子类就能出现
      JS 中使用较少(弱类型&继承使用较少)
    • I 接口独立
      保持接口的单一独立,避免出现“胖接口”
      JS 中没有接口(typescript 例外),使用较少
      类似于单一职责原则,这里更关注接口
    • D 依赖倒置
      面向接口编程,依赖于抽象而不依赖于具体
      使用方只关注接口而不关注具体类实现
      JS 中使用较少(没有接口&弱类型)

设计模式

从设计到模式

  • 创建型

    • 工厂模式
    • 单例模式
    • 原型模式
  • 结构型

    • 适配器模式
    • 装饰器模式
    • 代理模式
    • 外观模式
    • 桥接模式
    • 组合模式
    • 享元模式
  • 行为型

    • 策略模式
    • 迭代器模式
    • 模板方法模式
    • 职责连模式
    • 观察者模式
    • 命令模式
    • 备忘录模式
    • 状态模式
    • 访问者模式
    • 中介者模式
    • 解释器模式
  • 明白每个设计的道理和用意

  • 通过经典应用体会它的真正使用场景

  • 自己编码时多思考,尽量模仿

两个面试题

01

class Car {
  constructor(number, name) {
    this.number = number
    this.name = name
  }
}
class Kuaiche extends Car {
  constructor(number, name) {
    super(number, name)
    this.price = 1
  }
}
class Zhuanche extends Car {
  constructor(number, name) {
    super(number, name)
    this.price = 2
  }
}

class Trip {
  constructor(car) {
    this.car = car
  }
  start() {
    console.log(`行程开始,名称: ${this.car.name}, 车牌号: ${this.car.price}`)
  }
  end() {
    console.log('行程结束,价格: ' + (this.car.price * 5))
  }
}

let car = new Kuaiche(100, '桑塔纳')
let trip = new Trip(car)
trip.start()
trip.end()

02

image

// 车
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)

工厂模式

  • 介绍
  • UML && 代码演示
  • 使用场景

介绍:

  • 将 new 操作单独封装
  • 遇到 new 时,就要考虑是否该用工厂模式
class Product {
  constructor(name) {
    this.name = name
  }
  init() {

  }
  fun1() {

  }
}
class Creator {
  create(name) {
    return new Product(name)
  }
}
// test
let creator = new Creator()
let p1 = creator.create('p1')
p1.init()

场景:

  • jQuery:$('div')
    • $('div') 和 new $('div') 有何区别(查看上述 jQuery 例子)
  • React.createElement
  • vue 异步组件

学习经典库的方法:

  • 学习功能如何实现
  • 学习实现思路?怎么设计的
  • 强制自己写代码,模拟(刻意练习)
  • 模仿练习(拿来主义)

设计原则验证:

  • 构造函数和创建者分离
  • 符合开放封闭原则
  •  

单例模式

介绍:

  • 系统中被唯一使用的
  • 一个类只有一个实例。

登陆框、购物车...

说明:

  • 单例模式需要用到 java 的特性(private)
  • ES6 中没有(typescript 除外)
  • 只能用 java 代码来演示 UML 图的内容

场景:

  • jquery 只有一个 $
  • 模拟登陆框
  • 购物车
  • vuex 和 redux 中的 store

jQuery

// jQuery 只有一个 $
if (window.jQuery != null) {
  return window.jQuery
} else {
  // init
}
class SingleObject {
  login() {
    console.log('login...')
  }
}
SingleObject.getInstance = (function() {
  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)
class LoginForm {
  constructor() {
    this.state = 'hide'
  }
  show() {
    if (this.state === 'show') {
      console.log('已经显示')
      return
    }
    this.state = 'show'
    console.log('登录框已显示')
  }
  hide() {
    if (this.state === 'hide') {
      console.log('已经隐藏')
      return
    }
    this.state = 'hide'
    console.log('登录框已隐藏')
  }
}
LoginForm.getInstance = (function() {
  let instance
  return function() {
    if (!instance) {
      instance = new LoginForm();
    }
    return instance
  }
})()

// 一个页面中调用登录框
let login1 = LoginForm.getInstance()
login1.show()
  // login1.hide()

// 另一个页面中调用登录框
let login2 = LoginForm.getInstance()
login2.show()

// 两者是否相等
console.log('login1 === login2', login1 === login2)

设计原则验证:

  • 符合单一职责原则,只实例化唯一的对象
  • 没法具体开放封闭原则,但是绝对不违反开放封闭原则

适配器模式

插头,转接口...

介绍:

  • 旧接口格式和使用者不兼容
  • 中间加一个适配转换接口

UML类图:

 

演示:

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
// 当前ajax封装
ajax({
  url: 'getData',
  type: 'post',
  dataType: 'json',
  data: {
    id: "123"
  }
}).done(function(){})
// 历史代码
// $.ajax({...})

// 做一层适配器
var $ = {
  ajax: function(options) {
    return ajax(options)
  }
}

设计原则验证:

  • 将旧接口和使用者进行分离
  • 符合开放封闭原则

装饰器模式

介绍:

  • 为对象添加新功能
  • 不改变其原有的结构和功能

例子:手机壳

UML类图:

 

 

ES7 装饰器
babel插件:babel-plugin-transform-decorators-legacy
库:core-decorators(常用装饰器已经写好了,直接拿来用)

// 装饰类
// @testable
// class MyTestableClass {
//   // ...
// }

// function testable(target) {
//   target.isTestable = true;
// }

// console.log(MyTestableClass.isTestable) // true


function mixins(...list) {
  return function (target) {
    Object.assign(target.prototype, ...list)
  }
}

const Foo = {
  foo() { console.log('foo') }
}

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // 'foo'

// 装饰方法
function readonly(target, name, descriptor) {
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}

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

  @readonly
  name() {
    return `${this.first} ${this.last}` }
}

var p = new Person()
console.log(p.name())
p.name = function() {} // 这里会报错,因为 name 是只读属性


// another
function log(target, name, descriptor) {
  var oldValue = descriptor.value;

  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };

  return descriptor;
}

class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

const math = new Math();
const result = math.add(2, 4);
console.log('result', result);
// core-decorators


// import { readonly } from 'core-decorators'

// class Person {
//     @readonly
//     name() {
//         return 'zhang'
//     }
// }

// let p = new Person()
// console.log(p.name())
// // p.name = function () { /*...*/ }  // 此处会报错


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.

设计原则验证:

  • 将现有对象和装饰器进行分离,两者独立存在
  • 符合开放封闭原则

代理模式

介绍:

  • 使用者无权访问目标对象
  • 中间加代理,通过代理做授权和控制

例子:

  • 科学上网
  • 明星经纪人

UML类图:

 

演示:

 

场景:

  • 网页事件代理
  • jQuery $.proxy
     
  • ES6 Proxy
// 明星
let star = {
  name: '张XX',
  age: 25,
  phone: '13910733521'
}

// 经纪人
let agent = new Proxy(star, {
  get: function(target, key) {
    if (key === 'phone') {
      // 返回经纪人自己的手机号
      return '18611112222'
    }
    if (key === 'price') {
      // 明星不报价,经纪人报价
      return 120000
    }
    return target[key]
  },
  set: function(target, key, val) {
    if (key === 'customPrice') {
      if (val < 100000) {
        // 最低 10w
        throw new Error('价格太低')
      } else {
        target[key] = val
        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)

访问代理,接口地址是不会变的。
设计原则验证:

  • 代理类和目标类分离,隔离开目标类和使用者
  • 符合开放封闭原则

代理模式 VS 适配器模式

  • 适配器模式:提供一个不同的接口
  • 代理模式:提供一模一样的接口

代理模式 VS 装饰器模式

  • 装饰器模式:扩展功能,原有功能不变且可直接使用
  • 代理模式:显示原有功能,但是经过限制或者阉割之后的

外观模式

介绍:

  • 为子系统中的一组接口提供了一个高层接口
  • 使用者使用这个高层接口

 

观察者模式

介绍:

  • 发布 & 订阅
  • 一对多

UML类图:

 

例子:

  • 点咖啡,点好后坐等被叫
// 主题,接收状态变化,触发每个观察者
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  attach(observer) {
    this.observers.push(observer)
  }
  notifyAllObservers() {
    this.observers.forEach(observer => {
      observer.update()
    })
  }
}

// 观察者,等待被触发
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)

场景:

  • 网页事件绑定
  • Promise
     
  • jQuery callbacks
  • nodeJs 自定义事件
// jquery callbacks
var callbacks = $.Callbacks() // 注意大小写
callbacks.add(function(info) {
  console.log('fn1', info)
})
callbacks.add(function(info) {
  console.log('fn2', info)
})
callbacks.add(function(info) {
  console.log('fn3', info)
})
callbacks.fire('gogogo') // 发布
callbacks.fire('fire')
// nodejs 自定义事件
const EventEmitter = require('events').EventEmitter

const emitter1 = new EventEmitter()
emitter1.on('some', () => {
  // 监听 some 事件
  console.log('some event is occured 1')
})
emitter1.on('some', () => {
    // 监听 some 事件
    console.log('some event is occured 2')
  })
  // 触发 some 事件
emitter1.emit('some')


const emitter = new EventEmitter()
emitter.on('sbowName', name => {
  console.log('event occured ', name)
})
emitter.emit('sbowName', 'zhangsan') // emit 时候可以传递参数过去

---

// 任何构造函数都可以继承 EventEmitter 的方法 on emit
class Dog extends EventEmitter {
  constructor(name) {
    super()
    this.name = name
  }
}
var simon = new Dog('simon')
simon.on('bark', function() {
  console.log(this.name, ' barked')
})
setInterval(() => {
  simon.emit('bark')
}, 500)

node Stream 用到了自定义事件:

// 文件太大,用流的形式读取
// 监听文件有多少字符
var fs = require('fs')
var readStream = fs.createReadStream('./data/file1.txt') // 读取文件的 Stream

var length = 0
readStream.on('data', function(chunk) {
  length += chunk.toString().length
})
readStream.on('end', function() {
  console.log(length)
})

// 监听一行一行的数据(监听文件有多少行)
var readline = require('readline');
var fs = require('fs')

var rl = readline.createInterface({
  input: fs.createReadStream('./data/file1.txt')
});

var lineNum = 0
rl.on('line', function(line) {
  lineNum++
});
rl.on('close', function() {
  console.log('lineNum', lineNum)
});

其他场景:

  • nodeJs 中: 处理 http 请求;多进程通讯
  • vue 和 react 组件生命周期触发
  • vue watch
// node 处理 http 请求
var http = require('http')

function serverCallback(req, res) {
  var method = req.method.toLowerCase() // 获取请求的方法
  if (method === 'get') {}
  if (method === 'post') {
    // 接收 post 请求的内容
    var data = ''
    req.on('data', function(chunk) {
      // “一点一点”接收内容
      console.log('chunk', chunk.toString())
      data += chunk.toString()
    })
    req.on('end', function() {
      // 接收完毕,将内容输出
      console.log('end')
      res.writeHead(200, { 'Content-type': 'text/html' })
      res.write(data)
      res.end()
    })
  }

}
http.createServer(serverCallback).listen(8081) // 注意端口别和其他 server 的冲突
console.log('监听 8081 端口……')

设计原则验证:

  • 主题和观察者分离,不是主动触发而是被动监听,两者解耦
  • 符合开放封闭原则

迭代器模式

介绍:

  • 顺序访问一个集合(有序集合)
  • 使用者无需知道集合的内部结构(封装)

UML类图:

 

使用 jQuery 示例:

var arr = [1, 2, 3]
var nodeList = document.getElementsByTagName('p')
var $p = $('p')

// 要对这三个变量进行遍历,需要写三个遍历方法
// 第一
arr.forEach(function(item) {
    console.log(item)
  })
  // 第二
var i, length = nodeList.length
for (i = 0; i < length; i++) {
  console.log(nodeList[i])
}
// 第三
$p.each(function(key, p) {
  console.log(key, p)
})

// 如何能写出一个方法来遍历这三个对象呢
function each(data) {
  var $data = $(data) // 生成迭代器
  $data.each(function(key, p) {
    console.log(key, p)
  })
}
each(arr)
each(nodeList)
each($p)
class Iterator {
  constructor(conatiner) {
    this.list = conatiner.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
  }
  getIterator() {
    return new Iterator(this)
  }
}

// 测试代码
let container = new Container([1, 2, 3, 4, 5])
let iterator = container.getIterator()
while (iterator.hasNext()) {
  console.log(iterator.next())
}

使用场景:

  • JQuery each
  • ES6 Iterator

为何 ES6 Iterator 存在?

  • ES6 语法中,有序集合的数据类型已经很多
  • Array Map Set String TypedArray arguments NodeList
  • 需要有一个统一的遍历接口来遍历所有数据类型
  • 注意:object 不是有序集合,可以用 Map 代替

ES6 Iterator 是什么?

  • 以上数据类型,都有 [Symbol.iterator] 属性
  • 属性值是函数,执行函数返回一个迭代器
  • 这个迭代器就有 next 方法可顺序迭代子元素
  • 可运行 Array.prototype[Symbol.iterator] 来测试

ES6 Iterator 示例:

// 手动实现遍历器
function each(data) {
  // 生成遍历器
  let iterator = data[Symbol.iterator]()

  // console.log(iterator.next())  // 有数据时返回 {value: 1, done: false}
  // console.log(iterator.next())
  // console.log(iterator.next())
  // console.log(iterator.next())
  // console.log(iterator.next())  // 没有数据时返回 {value: undefined, done: true}

  let item = { done: false }
  while (!item.done) {
    item = iterator.next()
    if (!item.done) {
      console.log(item.value)
    }
  }
}

// 数组已经部署了 Iterator 接口
// for of 可以遍历 所有部署了 Iterator 接口的数据结构
function each(data) {
  for (let item of data) {
    console.log(item)
  }
}

let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 200)

each(arr)
each(nodeList)
each(m)

ES6 Iterator 与 Generator

  • Iterator 的价值不限于上述几个类型的遍历
  • 还有 Generator 函数的使用
  • 即只要返回的数据符合 Iterator 接口要求
  • 即可使用 Iterator 语法,这就是迭代器模式
// function* helloWorldGenerator() {
//   yield 'hello';
//   yield 'world';
//   return 'ending';
// }

// var hw = helloWorldGenerator();
// console.log(hw.next())
// console.log(hw.next())
// console.log(hw.next())
// console.log(hw.next())

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}

设计原则验证:

  • 迭代器对象和目标对象分离
  • 迭代器将使用者与目标对象分离开
  • 符合开放封闭原则

状态模式

介绍:

  • 一个对象有状态的变化
  • 每次状态变化都会触发一个逻辑
  • 不能总用 if...else 来控制

UML类图:

 

// 状态
class State {
  constructor(color) {
    this.color = color
  }
  handle(context) {
    console.log(`turn to ${this.color} light`)
    context.setState(this)
  }
}
// 主体
class Context {
  constructor() {
    this.state = null
  }
  setState(state) {
    this.state = state
  }
  getState() {
    return this.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())

场景:

  • 有限状态机
  • 写一个简单的 Promise
<script src="./03-javascript-state-machine.js"></script>
// 状态机模型
var fsm = new StateMachine({
  init: '收藏', // 初始状态,待收藏
  transitions: [{
    name: 'doStore',
    from: '收藏',
    to: '取消收藏'
  }, {
    name: 'deleteStore',
    from: '取消收藏',
    to: '收藏'
  }],
  methods: {
    // 监听执行收藏
    onDoStore: function() {
      console.log('收藏成功')
      updateText()
    },
    // 监听取消收藏
    onDeleteStore: function() {
      console.log('已取消收藏')
      updateText()
    }
  }
})

var $btn = $('#btn')

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

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

// 初始化文案
updateText()
// promise 简单实现
// 模型
var fsm = new StateMachine({
  init: 'pending',
  transitions: [{
    name: 'resolve',
    from: 'pending',
    to: 'fullfilled'
  }, {
    name: 'reject',
    from: 'pending',
    to: 'rejected'
  }],
  methods: {
    // 成功
    onResolve: function(state, data) {
      // 参数:state - 当前状态示例; data - fsm.resolve(xxx) 执行时传递过来的参数
      data.successList.forEach(fn => fn())
    },
    // 失败
    onReject: function(state, data) {
      // 参数:state - 当前状态示例; data - fsm.reject(xxx) 执行时传递过来的参数
      data.failList.forEach(fn => fn())
    }
  }
})

// 定义 Promise
class MyPromise {
  constructor(fn) {
    this.successList = []
    this.failList = []

    fn(() => {
      // resolve 函数
      fsm.resolve(this)
    }, () => {
      // reject 函数
      fsm.reject(this)
    })
  }
  then(successFn, failFn) {
    this.successList.push(successFn)
    this.failList.push(failFn)
  }
}

// 测试代码
function loadImg(src) {
  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'
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')
})

设计原则验证:

  • 将状态对象和主题对象分离,状态的变化逻辑单独处理
  • 符合开放封闭原则

其他设计模式

  • 不常用
  • 对应不到经典的应用场景

原型模式

  • clone 自己,生成一个新对象
  • java 默认有clone 接口,不用自己实现
    想一下:prototype
    应用:Object.create()

桥接模式

 

组合模式

  • 生成树形结构,表示“整体-部分”关系
  • 让整体和部分都具有一致的操作方式
  • 虚拟 DOM 中的 vnode 是这种形式,但数据类型简单

 

享元模式(共享元数据)

  • 共享内存
  • 相同数据,共享使用
  • 客户端不怎么考虑内存

例子:事件代理(ul>li)

策略模式

  • 不同策略分开处理
  • 避免出现大量 if...else 或者 switch...case

模板方法模式

class Action {
  handle() {
    this.handle1()
    this.handle2()
    this.handle3()
  }
  handle1() {

  }
  handle2() {
    
  }
  handle3() {
    
  }
}

职责链模式

  • 一步操作可能分为多个职责角色来完成
  • 把这些角色都分开,然后用一个链串起来
  • 将发起者和各个处理者进行隔离
class Action {
  constructor(name) {
    this.name = name
    this.nextAction = null
  }
  setNextAction(action) {
    this.nextAction = action
  }
  handle() {
    console.log(`${this.name} 审批`)
    if (this.nextAction !== null) {
      this.nextAction.handle()
    }
  }
}
// test
let a1 = new Action('组长')
let a2 = new Action('主管')
let a2 = new Action('经理')
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

命令模式

  • 执行命令时,发布者和执行者分开
  • 将军向小号手下达指令,小号手让士兵执行
  • JS应用
    • 网页富文本编辑器操作,浏览器封装了一个命令对象
    • document.execCommand('bold')
    • document.execCommand('undo')

 

备忘录模式

  • 随时记录一个对象的状态变化
  • 随时可以恢复之前的某个状态(如撤销)

编辑器案例:备忘列表存储编辑器中设置的每一项设置的内容

中介者模式

联想现实中介。1
 

访问者模式&解释器模式

关于面试

重点设计模式要深入理解
非常用的,视业务场景需要选择

综合应用

介绍:

  • 用 jQuery 做一个模拟购物车的示例
  • 功能:显示购物列表、加入购物车、从购物车删除
  • 设计模式:工厂、单例、装饰器、观察者、状态、模板方法、代理

UML类图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值