装饰器模式

👉 描述

  • 动态的给函数赋能
  • 增强普通对象的功能,按照顺序进行装饰。

👉 场景

  • react-rudex connect函数

👉 实现

//装饰者模式
//增加普通对象的功能

function Sale(price) {
    this.price = price;
    this.decorators_list = [];
}

Sale.decorators = {};
Sale.decorators.fedtax = {
    getPrice: function (price) {
        return price + price * 5 / 100;
    }
};
Sale.decorators.money = {
    getPrice: function (price) {
        return '$' + price.toFixed(2);
    }
};

Sale.prototype.decorate = function (decorator) {
    this.decorators_list.push(decorator);  
};

Sale.prototype.getPrice = function () {
    var price = this.price,
    i,
    len = this.decorators_list.length,
    name;
    for (i = 0; i < len; i++) {
        name = this.decorators_list[i];
        price = Sale.decorators[name].getPrice(price);
    }
    return price;
};

var sale = new Sale(100);
sale.decorate('fedtax');
sale.getPrice();  //105
sale.decorate('money');
sale.getPrice();  //$105.00
 //    生活中的例子: 天气冷了, 就添加衣服来保暖;天气热了, 就将外套脱下;这个例子很形象地含盖了装饰器的神韵, 随着天气的冷暖变化, 衣服可以动态的穿上脱下。
let wear = function() {
  console.log('穿上第一件衣服')
}

const _wear1 = wear

wear = function() {
  _wear1()
  console.log('穿上第二件衣服')
}

const _wear2 = wear

wear = function() {
  _wear2()
  console.log('穿上第三件衣服')
}

wear()

// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服

这种方式有以下缺点: 1: 临时变量会变得越来越多;2: this 指向有时会出错

// AOP装饰函数
// 前置代码
Function.prototype.before = function(fn) {
  const self = this
  return function() {
    fn.apply(new(self), arguments)  // https://github.com/MuYunyun/blog/pull/30#event-1817065820
    return self.apply(new(self), arguments)
  }
}

// 后置代码
Function.prototype.after = function(fn) {
  const self = this
  return function() {
    self.apply(new(self), arguments)
    return fn.apply(new(self), arguments)
  }
}

用后置代码来实验下上面穿衣服的 demo,

const wear1 = function() {
  console.log('穿上第一件衣服')
}

const wear2 = function() {
  console.log('穿上第二件衣服')
}

const wear3 = function() {
  console.log('穿上第三件衣服')
}

const wear = wear1.after(wear2).after(wear3)
wear()

// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服

但这样子有时会污染原生函数, 可以做点通变

const after = function(fn, afterFn) {
  return function() {
    fn.apply(this, arguments)
    afterFn.apply(this, arguments)
  }
}

const wear = after(after(wear1, wear2), wear3)
wear()

发布-订阅模式

👉 描述

  • 发布者中保存着一份订阅者的列表,当发布者的状态发生改变的时候就主动向订阅者发出通知
  • PubSub

👉 场景

  • 瀑布流库

👉 实现

瀑布流库实现代码


//发布订阅模式
//当发生一个感兴趣的事件时,将该事件通告给所有订阅者
//形成对象之间的松散耦合

function EventTarget() {
    this.handlers = {};
}
EventTarget.prototype = {
    constructor: EventTarget,
    addHandler: function (type, handler) {
        if (typeof this.handlers[type] == "undefined") {
            this.handlers[type] = [];
        }
        this.handlers[type].push(handler);
    },
    fire: function (event) {
        if (!event.target) {
            event.target = this;
        }
        if (this.handlers[event.type] instanceof Array) {
            var handlers = this.handlers[event.type];
            for (var i = 0, len = handlers.length; i < len; i++) {
                handlers[i](event);
            }
        }
    },
    removeHandler: function (type, handler) {
        if (this.handlers[type] instanceof Array) {
            var handlers = this.handlers[type];
            for (var i = 0, len = handlers.length; i < len; i++) {
                if (handlers[i] === handler) {
                    break;
                }
            }
            handlers.splice(i, 1);
        }
    }
};

var publisher = new EventTarget();  //定义发布者
//向发布者中注册订阅者
publisher.addHandler('call1',function () {
    console.log('call1');
});
publisher.addHandler('call1',function () {
    console.log('call1 again');
});
//发布者发布消息
publisher.fire({type: 'call1'});  //call1 call1 again

单例模式

👉 描述

  • 保证一个对象只有一个实例、第二次创建的实例和第一次创建的完全一样(一个类只能构造出唯一的实例)
  • 可以全局访问

👉 场景

  • 弹框、全局缓存

👉 实现

//静态属性方式实现
function Person() {
    if (typeof Person.instance === 'object') {
        return Person.instance;
    }
    this.name = 'vector';
    this.age = 25;
    Person.instance = this;
}

//闭包方式实现
var Person = (function () {
   var instance;
   return function (name, age) {
       if (instance) {
           return instance;
       }
       this.name = 'vector';
       this.age = 25;
       instance = this;
   };
}());

var p1 = new Person();
var p2 = new Person();
console.log(p1 === p2);  //true
© 2019 GitHub, Inc.

策略模式

👉 描述

  • 根据不同命令命中不同算法、可以避免使用多重描述语句

👉 场景

  • 大量if语句

👉 实现

//策略模式
//根据不同命令可以命中不同的算法

var operate = {
    add: function (a, b) {
        return a + b;
    },
    sub: function (a, b) {
        return a - b;
    },
    mul: function (a, b) {
        return a * b;
    },
    div: function (a, b) {
        return a / b;
    }
};

var calc = function (cmd, arg1, arg2) {
    return operate[cmd](arg1, arg2);
};

calc('add', 1, 3);  //4
calc('mul', 2, 4);  //8

const S = function(salary) {
  return salary * 4
}

const A = function(salary) {
  return salary * 3
}

const B = function(salary) {
  return salary * 2
}

const calculateBonus = function(func, salary) {
  return func(salary)
}

calculateBonus(A, 10000) // 30000

代理模式

👉 描述

  • 代理对象和本身对象具有一致的接口、在 JS 中最常用的为虚拟代理和缓存代理。
  • 通过包装一个对象以控制对它的访问、ES6中的Proxy就是通过代理扩展对象功能

👉 场景

  • 图片预加载

👉 实现

// 虚拟代理实现图片预加载
const myImage = (function() {
  const imgNode = document.createElement('img')
  document.body.appendChild(imgNode)
  return {
    setSrc: function(src) {
      imgNode.src = src
    }
  }
})()

const proxyImage = (function() {
  const img = new Image()
  img.onload = function() { // http 图片加载完毕后才会执行
    myImage.setSrc(this.src)
  }
  return {
    setSrc: function(src) {
      myImage.setSrc('loading.jpg') // 本地 loading 图片
      img.src = src
    }
  }
})()

proxyImage.setSrc('http://loaded.jpg')

/代理模式
//通过包装一个对象以控制对它的访问

//以缓存代理为例
const requestResult = async function (id) {
    let requestConfig = {
        credentials: 'include',
        method: 'GET',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        mode: "cors",
        cache: "force-cache"
    };
    let url = '/service/v1/' + id;
    const response = await fetch(url, requestConfig);
    const responseJson = await response.json();
    return responseJson;
};

const proxy = {
    cache: {},
    request: async function (id) {
        if (this.cache[id]) {
            //直接取缓存内容
            return this.cache[id];
        } else {
            return requestResult(id);
        }
    }
};

proxy.request(1);
proxy.request(1);  //从缓存获取

//  缓存代理实现乘积计算
const mult = function() {
  let a = 1
  for (let i = 0, l; l = arguments[i++];) {
    a = a * l
  }
  return a
}

const proxyMult = (function() {
  const cache = {}
  return function() {
    const tag = Array.prototype.join.call(arguments, ',')
    if (cache[tag]) {
      return cache[tag]
    }
    cache[tag] = mult.apply(this, arguments)
    return cache[tag]
  }
})()

proxyMult(1, 2, 3, 4) // 24

迭代器模式

👉 描述

  • 为遍历一个数据结构提供方法
  • 能获取聚合对象的顺序和元素

👉 场景

  • 遍历数据结构
  • each([1,2,3],callback)

👉 实现

//迭代器模式
//遍历某一个数据结构,这里以数组为例

var iterator = (function () {
    var index = 0,
        data = [1, 2, 3, 4, 5],
        len = data.length;
    return {
        next: function () {
            if (!this.hasNext()) {
                return null;
            }
            return data[index++];
        },
        hasNext: function () {
            return index < len;
        }
    };
}());

while(iterator.hasNext()) {
    console.log(iterator.next());
}

命令模式

👉 描述

  • 不同对象间约定好相应的接口
  • 命令模式与策略模式有些类似, 在 JavaScript 中它们都是隐式的。

👉 场景

  • 按钮和命令的分离

👉 实现

const setCommand = function(button, command) {
  button.onClick = function() {
    command.excute()
  }
}

// --------------------  上面的界面逻辑由A完成, 下面的由B完成

const menu = {
  updateMenu: function() {
    console.log('更新菜单')
  },
}

const UpdateCommand = function(receive) {
  return {
    excute: receive.updateMenu,
  }
}

const updateCommand = UpdateCommand(menu) // 创建命令

const button1 = document.getElementById('button1')
setCommand(button1, updateCommand)

组合模式

👉 描述

  • 组合模式在对象间形成树形结构;
  • 组合模式中基本对象和组合对象被一致对待;
  • 无须关心对象有多少层, 调用时只需在根部进行调用;

👉 场景

  • 扫描文件夹

👉 实现

//    命令模式和组合模式的实现
const MacroCommand = function() {
  return {
    lists: [],
    add: function(task) {
      this.lists.push(task)
    },
    excute: function() { // ①: 组合对象调用这里的 excute,
      for (let i = 0; i < this.lists.length; i++) {
        this.lists[i].excute()
      }
    },
  }
}

const command1 = MacroCommand() // 基本对象

command1.add({
  excute: () => console.log('煮咖啡') // ②: 基本对象调用这里的 excute,
})

const command2 = MacroCommand() // 组合对象

command2.add({
  excute: () => console.log('打开电视')
})

command2.add({
  excute: () => console.log('打开音响')
})

const command3 = MacroCommand()

command3.add({
  excute: () => console.log('打开空调')
})

command3.add({
  excute: () => console.log('打开电脑')
})

const macroCommand = MacroCommand()
macroCommand.add(command1)
macroCommand.add(command2)
macroCommand.add(command3)

macroCommand.excute()

// 煮咖啡
// 打开电视
// 打开音响
// 打开空调
// 打开电脑
//   扫描文件夹
const Folder = function(folder) {
  this.folder = folder
  this.lists = []
}

Folder.prototype.add = function(resource) {
  this.lists.push(resource)
}

Folder.prototype.scan = function() {
  console.log('开始扫描文件夹: ', this.folder)
  for (let i = 0, folder; folder = this.lists[i++];) {
    folder.scan()
  }
}

const File = function(file) {
  this.file = file
}

File.prototype.add = function() {
  throw Error('文件下不能添加其它文件夹或文件')
}

File.prototype.scan = function() {
  console.log('开始扫描文件: ', this.file)
}

const folder = new Folder('根文件夹')
const folder1 = new Folder('JS')
const folder2 = new Folder('life')

const file1 = new File('深入React技术栈.pdf')
const file2 = new File('JavaScript权威指南.pdf')
const file3 = new File('小王子.pdf')

folder1.add(file1)
folder1.add(file2)

folder2.add(file3)

folder.add(folder1)
folder.add(folder2)

folder.scan()

// 开始扫描文件夹:  根文件夹
// 开始扫描文件夹:  JS
// 开始扫描文件:  深入React技术栈.pdf
// 开始扫描文件:  JavaScript权威指南.pdf
// 开始扫描文件夹:  life
// 开始扫描文件:  小王子.pdf

模板方法模式

👉 描述

  • 在继承的基础上, 在父类中定义好执行的算法。

👉 场景

  • 泡茶与泡咖啡(大致过程一致、部分过程不一致)

👉 实现

const Drinks = function() {}

Drinks.prototype.firstStep = function() {
  console.log('烧开水')
}

Drinks.prototype.secondStep = function() {}

Drinks.prototype.thirdStep = function() {
  console.log('倒入杯子')
}

Drinks.prototype.fourthStep = function() {}

Drinks.prototype.init = function() { // 模板方法模式核心: 在父类上定义好执行算法
  this.firstStep()
  this.secondStep()
  this.thirdStep()
  this.fourthStep()
}

const Tea = function() {}

Tea.prototype = new Drinks

Tea.prototype.secondStep = function() {
  console.log('浸泡茶叶')
}

Tea.prototype.fourthStep = function() {
  console.log('加柠檬')
}

const Coffee = function() {}

Coffee.prototype = new Drinks

Coffee.prototype.secondStep = function() {
  console.log('冲泡咖啡')
}

Coffee.prototype.fourthStep = function() {
  console.log('加糖')
}

const tea = new Tea()
tea.init()

// 烧开水
// 浸泡茶叶
// 倒入杯子
// 加柠檬

const coffee = new Coffee()
coffee.init()

// 烧开水
// 冲泡咖啡
// 倒入杯子
// 加糖

//  ****   钩子(加佐料)     ****

Drinks.prototype.ifNeedFlavour = function() { // 加上钩子
  return true
}

Drinks.prototype.init = function() { // 模板方法模式核心: 在父类上定义好执行算法
  this.firstStep()
  this.secondStep()
  this.thirdStep()
  if (this.ifNeedFlavour()) { // 默认是 true, 也就是要加调料
    this.fourthStep()
  }
}

// ...
const Coffee = function() {}

Coffee.prototype = new Drinks()
// ...

Coffee.prototype.ifNeedFlavour = function() {
  return window.confirm('是否需要佐料吗?') // 弹框选择是否佐料
}

享元模式

👉 描述

  • 享元模式是一种优化程序性能的模式, 本质为减少对象创建的个数。

👉 场景

  • 有大量相似的对象, 占用了大量内存
  • 对象中大部分状态可以抽离为外部状态

👉 实现

男女模型试穿衣服

职责链模式

👉 描述

  • 类似多米诺骨牌, 通过请求第一个描述, 会持续执行后续的描述, 直到返回结果为止。

👉 场景

  • 在项目中能对 if-else 语句进行优化

👉 实现

场景: 某电商针对已付过定金的用户有优惠政策, 在正式购买后, 已经支付过 500 元定金的用户会收到 100 元的优惠券, 200 元定金的用户可以收到 50 元优惠券, 没有支付过定金的用户只能正常购买。

职责链模式比较重要, 项目中能用到它的地方会有很多, 用上它能解耦 1 个请求对象和 n 个目标对象的关系。

中介者模式

👉 描述

  • 对象和对象之间借助第三方中介者进行通信。。

👉 场景
+

👉 实现

// 一场测试结束后, 公布结果: 告知解答出题目的人挑战成功, 否则挑战失败。
const player = function(name) {
  this.name = name
  playerMiddle.add(name)
}

player.prototype.win = function() {
  playerMiddle.win(this.name)
}

player.prototype.lose = function() {
  playerMiddle.lose(this.name)
}

const playerMiddle = (function() { // 将就用下这个 demo, 这个函数当成中介者
  const players = []
  const winArr = []
  const loseArr = []
  return {
    add: function(name) {
      players.push(name)
    },
    win: function(name) {
      winArr.push(name)
      if (winArr.length + loseArr.length === players.length) {
        this.show()
      }
    },
    lose: function(name) {
      loseArr.push(name)
      if (winArr.length + loseArr.length === players.length) {
        this.show()
      }
    },
    show: function() {
      for (let winner of winArr) {
        console.log(winner + '挑战成功;')
      }
      for (let loser of loseArr) {
        console.log(loser + '挑战失败;')
      }
    },
  }
}())

const a = new player('A 选手')
const b = new player('B 选手')
const c = new player('C 选手')

a.win()
b.win()
c.lose()

// A 选手挑战成功;
// B 选手挑战成功;
// C 选手挑战失败;

在这段代码中 A、B、C 之间没有直接发生关系, 而是通过另外的 playerMiddle 对象建立链接, 姑且将之当成是中介者模式了。

状态模式

👉 描述

  • 将事物内部的每个状态分别封装成类, 内部状态改变会产生不同行为。
  • 优点: 用对象代替字符串记录当前状态, 状态易维护
  • 缺点: 需编写大量状态类对象

👉 场景
+

👉 实现

//  某某牌电灯, 按一下按钮打开弱光, 按两下按钮打开强光, 按三下按钮关闭灯光。
// 将状态封装成不同类
const weakLight = function(light) {
  this.light = light
}

weakLight.prototype.press = function() {
  console.log('打开强光')
  this.light.setState(this.light.strongLight)
}

const strongLight = function(light) {
  this.light = light
}

strongLight.prototype.press = function() {
  console.log('关灯')
  this.light.setState(this.light.offLight)
}

const offLight = function(light) {
  this.light = light
}

offLight.prototype.press = function() {
  console.log('打开弱光')
  this.light.setState(this.light.weakLight)
}

const Light = function() {
  this.weakLight = new weakLight(this)
  this.strongLight = new strongLight(this)
  this.offLight = new offLight(this)
  this.currentState = this.offLight          // 初始状态
}

Light.prototype.init = function() {
  const btn = document.createElement('button')
  btn.innerHTML = '按钮'
  document.body.append(btn)
  const self = this
  btn.addEventListener('click', function() {
    self.currentState.press()
  })
}

Light.prototype.setState = function(state) { // 改变当前状态
  this.currentState = state
}

const light = new Light()
light.init()

// 打开弱光
// 打开强光
// 关灯
// 非面向对象实现的状态模式
// 借助于 JavaScript 的委托机制, 可以像如下实现状态模式:
const obj = {
  'weakLight': {
    press: function() {
      console.log('打开强光')
      this.currentState = obj.strongLight
    }
  },
  'strongLight': {
    press: function() {
      console.log('关灯')
      this.currentState = obj.offLight
    }
  },
  'offLight': {
    press: function() {
      console.log('打开弱光')
      this.currentState = obj.weakLight
    }
  },
}

const Light = function() {
  this.currentState = obj.offLight
}

Light.prototype.init = function() {
  const btn = document.createElement('button')
  btn.innerHTML = '按钮'
  document.body.append(btn)
  const self = this
  btn.addEventListener('click', function() {
    self.currentState.press.call(self) // 通过 call 完成委托
  })
}

const light = new Light()
light.init()

适配者模式

👉 描述

  • 主要用于解决两个接口之间不匹配的问题。

👉 场景

  • 枚举接口变更

👉 实现

// 老接口
const zhejiangCityOld = (function() {
  return [
    {
      name: 'hangzhou',
      id: 11,
    },
    {
      name: 'jinhua',
      id: 12
    }
  ]
}())

console.log(getZhejiangCityOld())

// 新接口希望是下面形式
{
  hangzhou: 11,
  jinhua: 12,
}

// 这时候就可采用适配者模式
const adaptor = (function(oldCity) {
  const obj = {}
  for (let city of zhejiangCityOld) {
    obj[city.name] = city.id
  }
  return obj
}())

观察者模式

👉 描述

  • MVVM框架原理

👉 场景

  • Vue双向数据绑定

👉 实现

MVVM 框架解析之双向绑定