书单
《JavaScript 设计模式与开发实践》
《设计模式之美》
《掘金小册-JavaScript 设计模式核⼼原理与应⽤实践》
《珠峰-设计模式》
代码
- 单例模式
class Author {
constructor({ name, age }) {
this.name = name;
this.age = age;
}
static getInstance({ name, age }) {
if (!Author._instance) {
Author._instance = new Author({ name, age });
}
return Author._instance;
}
}
const adi1 = Author.getInstance({ name: "adi", age: 24 });
const adi2 = Author.getInstance({ name: "adi", age: 24 });
const adi3 = new Author({ name: "adi", age: 24 });
const adi4 = new Author({ name: "adi", age: 24 });
console.log("adi1 === adi2", adi1 === adi2); // true
console.log("adi3 === adi4", adi3 === adi4); // false
// or
function Author2({ name, age }) {
this.name = name;
this.age = age;
}
Author2.getInstance = (function () {
let _instance;
return function ({ name, age }) {
if (!_instance) {
_instance = new Author2({ name, age });
}
return _instance;
};
})();
const adi5 = Author2.getInstance({ name: "adi", age: 24 });
const adi6 = Author2.getInstance({ name: "adi", age: 24 });
const adi7 = new Author2({ name: "adi", age: 24 });
const adi8 = new Author2({ name: "adi", age: 24 });
console.log("adi5 === adi6", adi5 === adi6); // true
console.log("adi7 === adi8", adi7 === adi8); // false
// 通用的惰性单例
const getSingle = function (fn) {
const _instance;
return function () {
return _instance || (_instance = fn.apply(this, arguments));
};
};
- 策略模式
// 使用策略模式处理胖逻辑,遵循函数开放封闭原则、提升函数复用性
// before
const calculateBonus = function (performanceLevel, salary) {
if (performanceLevel === "S") {
return salary * 4;
}
if (performanceLevel === "A") {
return salary * 3;
}
if (performanceLevel === "B") {
return salary * 2;
}
};
calculateBonus("B", 20000); // 40000
calculateBonus("S", 6000); // 24000
// after
const strategies = {
S: function (salary) {
return salary * 4;
},
A: function (salary) {
return salary * 3;
},
B: function (salary) {
return salary * 2;
}
};
const calculateBonus = function (level, salary) {
return strategies[level](salary);
};
calculateBonus("S", 20000); // 80000
calculateBonus("A", 10000); // 30000
- 代理模式
// 由于一个对象不能直接引用另外一个对象,所以需要通过代理对象在这两个对象之间起到中介作用
class PreLoadImage {
constructor(imgNode) {
// 获取真实的DOM节点
this.imgNode = imgNode;
}
// 操作img节点的src属性
setSrc(imgUrl) {
this.imgNode.src = imgUrl;
}
}
class ProxyImage {
// 占位图的url地址
static LOADING_URL = "xxxxxx";
constructor(targetImage) {
// 目标Image,即PreLoadImage实例
this.targetImage = targetImage;
}
// 该方法主要操作虚拟Image,完成加载
setSrc(targetUrl) {
// 真实img节点初始化时展示的是一个占位图
this.targetImage.setSrc(ProxyImage.LOADING_URL);
// 创建一个帮我们加载图片的虚拟Image实例
const virtualImage = new Image();
// 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
virtualImage.onload = () => {
this.targetImage.setSrc(targetUrl);
};
// 设置src属性,虚拟Image实例开始加载图片
virtualImage.src = targetUrl;
}
}
- 发布订阅模式
// 定义一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。 —— Graphic Design Patterns
// 通用实现
const event = {
clientList: [],
listen(key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进缓存列表
},
trigger(key, ...args) {
var key = key,
fns = this.clientList[key];
if (!fns || fns.length === 0) {
// 如果没有绑定对应的消息
return false;
}
for (let i = 0, fn; (fn = fns[i++]); ) {
fn.apply(this, args);
}
},
remove(key, fn) {
const fns = this.clientList[key];
if (!fns) {
// 如果 key 对应的消息没有被人订阅,则直接返回
return false;
}
if (!fn) {
// 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
fns && (fns.length = 0);
} else {
for (let l = fns.length - 1; l >= 0; l--) {
// 反向遍历订阅的回调函数列表
const _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1); // 删除订阅者的回调函数
}
}
}
}
};
// 为对象添加发布订阅功能
const installEvent = function (obj) {
for (const i in event) {
obj[i] = event[i];
}
};
// DEMO
const salesOffices = {};
installEvent(salesOffices);
salesOffices.listen("squareMeter88", function (price) {
// 小明订阅消息
console.log("价格= " + price);
});
salesOffices.listen("squareMeter100", function (price) {
// 小红订阅消息
console.log("价格= " + price);
});
salesOffices.trigger("squareMeter88", 2000000); // 输出:2000000
salesOffices.trigger("squareMeter100", 3000000); // 输出:3000000
salesOffices.remove("squareMeter100");
// EventBus
class EventEmitter {
constructor() {
this.handlers = {}
}
on(eventName, cb) {
if (!this.handlers[eventName])
this.handlers[eventName] = []
}
this.handlers[eventName].push(cb)
}
emit(eventName, ...args) {
if (this.handlers[eventName]) {
this.handlers[eventName].forEach((callback) => {
callback(...args)
})
}
}
off(eventName, cb) {
const callbacks = this.handlers[eventName]
const index = callbacks.indexOf(cb)
if (index !== -1) {
callbacks.splice(index, 1)
}
}
once(eventName, cb) {
const wrapper = (...args) => {
cb.apply(...args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}
// 观察者模式与发布-订阅模式的区别是什么?
观察者模式和发布-订阅模式之间的区别,在于是否存在第三方、发布者能否直接感知订阅者。
观察者模式:
被观察者 <<--->> 观察者
发布订阅模式:
发布者 --->> 事件中心 <<-->>订阅者
- 适配器模式
// 适配器模式通过把一个类的接口变换成客户端所期待的另一种接口, 例如Node 的BFF层
class Socket {
output() {
return '输出220V';
}
}
abstract class Power {
abstract charge(): string;
}
class PowerAdapter extends Power {
constructor(public socket: Socket) {
super();
}
//转换后的接口和转换前不一样
charge() {
return this.socket.output() + ' 经过转换 输出24V';
}
}
let powerAdapter = new PowerAdapter(new Socket());
console.log(powerAdapter.charge());
- 装饰器模式
// 在不改变其原有的结构和功能为对象添加新功能的模式其实就叫做装饰器模式
function people(height, weight, character) {
this.height = 170;
this.weight = 80;
this.character = 'normal';
return this;
}
const xiaowang = people();
function decorate(ctr) {
ctr.height = 180;
ctr.weight = 70;
ctr.character = 'handsome';
return ctr;
}
// Decorator
function Decorator(target) {
target.control = function() {
console.log('我是新的逻辑')
}
return target
}
@Decorator
class HorribleCode () {
//老代码逻辑
}
HorribleCode.control()
- 状态模式
class Light {
constructor() {
this.status = ['opened', 'closed'];
this.curStatus = -1;
}
setStatus(status) {
this.status = status;
}
press() {
this.curStatus = (this.curStatus + 1 === this.status.length) ? 1 : this.curStatus + 1;
console.log(this.status[this.curStatus]);
}
}
const light = new Light();
light.setStatus(['weak', 'common', 'environmental', 'closed']);
light.press(); // weak
light.press(); // common
light.press(); // environmental
light.press(); // closed
- 迭代器模式
// 迭代器模式用于顺序地访问聚合对象内部的元素,无需知道对象内部结构。
function iteratorGenerator(list) {
// idx记录当前访问的索引
var idx = 0
// len记录传入集合的长度
var len = list.length
return {
// 自定义next方法
next: function() {
// 如果索引还没有超出集合长度,done为false
var done = idx >= len
// 如果done为false,则可以继续取值
var value = !done ? list[idx++] : undefined
// 将当前值与遍历是否完毕(done)返回
return {
done: done,
value: value
}
}
}
}
var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手'])
iterator.next()
iterator.next()
iterator.next()