class Sington {constructor(key, value) {this.key = key;this.value = value;}static getInstance(key, value) {if (!this.instance) {this.instance = new Sington(key, value);}return this.instance;}}const a1 = Sington.getInstance("Drex", "Wang1");const a2 = Sington.getInstance("Drex", "Wang2");const a3 = Sington.getInstance("Drex", "Wang3");console.log(a1 === a2);console.log(a1 === a3);
工厂模式是另外一种关注对象创建概念的创建模式。它的领域中同其它模式的不同之处在于它并没有明确要求我们使用一个构造器。取而代之,一个工厂能提供一个创建对象的公共接口,我们可以在其中指定我们希望被创建的工厂对象的类型。
简单工厂模式
class User {constructor(arg) {this.name = arg.name;this.menu = arg.menu;}static getInstance(role) {switch (role) {case "Administrator":return new User({name: "超级管理员",menu: ["首页", "商品管理", "资讯管理", "系统管理"],});break;case "Admin":return new User({name: "管理员",menu: ["首页", "商品管理", "资讯管理"],});break;case "User":return new User({ name: "用户", menu: ["首页", "商品管理"] });break;default:throw new Error("参数错误:可选Administrator,Admin,User");}}}const administrator = User.getInstance("Administrator");const admin = User.getInstance("Admin");const user = User.getInstance("user");
工厂方法模式
class User {constructor(name = "", menu = []) {if (new.target === User) {throw new Error("抽象类不能实例化!");}this.name = name;this.menu = menu;}}class UserFactory extends User {constructor(name, menu) {super(name, menu);}create(role) {switch (role) {case "administrator":return new UserFactory("超级管理员", ["首页","通讯录","发现页","应用数据","权限管理",]);break;case "admin":return new UserFactory("普通用户", ["首页", "通讯录", "发现页"]);break;case "user":return new UserFactory("普通用户", ["首页", "通讯录", "发现页"]);break;default:throw new Error("参数错误, 可选参数:administrator、admin、user");}}}let userFactory = new UserFactory();let administrator = userFactory.create("administrator");let admin = userFactory.create("admin");let user = userFactory.create("user");
抽象工厂模式
class User {constructor(type) {if (new.target === User) {throw new Error("抽象类不能实例化!");}this.type = type;}}class UserOfWechat extends User {constructor(name) {super("wechat");this.name = name;this.viewPage = ["首页", "通讯录", "发现页"];}}class UserOfQq extends User {constructor(name) {super("qq");this.name = name;this.viewPage = ["首页", "通讯录", "发现页"];}}class UserOfWeibo extends User {constructor(name) {super("weibo");this.name = name;this.viewPage = ["首页", "通讯录", "发现页"];}}function getAbstractUserFactory(type) {switch (type) {case "wechat":return UserOfWechat;break;case "qq":return UserOfQq;break;case "weibo":return UserOfWeibo;break;default:throw new Error("参数错误, 可选参数:administrator、admin、user");}}let WechatUserClass = getAbstractUserFactory("wechat");let QqUserClass = getAbstractUserFactory("qq");let WeiboUserClass = getAbstractUserFactory("weibo");let wechatUser = new WechatUserClass("微信小李");let qqUser = new QqUserClass("QQ小李");let weiboUser = new WeiboUserClass("微博小李");
建造者模式的特点是分步构建一个复杂的对象,可以用不同组合或顺序建造出不同意义的对象,通常使用者并不需要知道建造的细节,通常使用链式调用来进行建造过程,最后调用 build 方法来生成最终对象。
同样作为创建型的设计模式,需要注意和工厂模式的区别,工厂虽然也是创建对象,但怎样创建无所谓,工厂模式关注的是创建的结果;而建造者模式不仅得到了结果,同时也参与了创建的具体过程,适合用来创建一个复杂的复合对象。
class baseBuilder {// 创建init() {Object.keys(this).forEach((key) => {// 方法名const funName = `with${key.substring(0, 1).toUpperCase()}${key.substring(1)}`;this[funName] = (val) => {this[key] = val;return this;};});}// 构建build() {// 计算属性keyconst keys = Object.keys(this).filter((key) => typeof this[key] !== "function");// 返回属性值return keys.reduce((prev, next) => ({ ...prev, [next]: this[next] }), {});}}class carBuilder extends baseBuilder {constructor() {super();this.name = "";this.brand = "";this.model = "";this.year = "";super.init();}}class userBuilder extends baseBuilder {constructor() {super();this.name = "";this.sex = "";this.age = "";super.init();}}const car = new carBuilder().withName("大奔").withBrand("奔驰").withModel("E300").withYear("2020").build();const people = new userBuilder().withName("Drex").withSex("男").withAge("30").build();
缓存代理可以将一些开销很大的方法的运算结果进行缓存,再次调用该函数时,若参数一致,则可以直接返回缓存中的结果,而不用再重新进行运算。
// 缓存的斐波那契数列const getFib = (number) => {if (number <= 2) {return 1;} else {return getFib(number - 1) + getFib(number - 2);}};const getCacheProxy = (fn, cache = new Map()) => {return new Proxy(fn, {apply(target, context, args) {const argsString = args.join(" ");if (cache.has(argsString)) {// 如果有缓存,直接返回缓存数据console.log(`输出${args}的缓存结果: ${cache.get(argsString)}`);return cache.get(argsString);}const result = fn(...args);cache.set(argsString, result);return result;},});};const getFibProxy = getCacheProxy(getFib);getFibProxy(40); // 102334155getFibProxy(40); // 输出40的缓存结果: 102334155
// 表单验证const userForm = {account: "",password: "",};// 验证方法const validators = {account(value) {// account 只允许为中文const re = /^[\u4e00-\u9fa5]+$/;return {valid: re.test(value),error: '"account" is only allowed to be Chinese',};},password(value) {// password 的长度应该大于6个字符return {valid: value.length >= 6,error: '"password "should more than 6 character',};},};// 表单验证器const getValidateProxy = (target, validators) =>new Proxy(target, {_validators: validators,set(target, props, value) {if (value === "") {console.error(`"${props}" is not allowed to be empty`);return (target[props] = false);}// 执行判断条件const validResult = this._validators[props](value);if (validResult.valid) {return Reflect.set(target, props, value);} else {console.error(`${validResult.error}`);return (target[props] = false);}},});const userFormProxy = getValidateProxy(userForm, validators);userFormProxy.account = "123"; // "account" is only allowed to be ChineseuserFormProxy.password = "he"; // "password "should more than 6 character
// 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理function debounce(fn, delay) {delay = delay || 200;let timer = null;return function() {let arg = arguments;// 每次操作时,清除上次的定时器clearTimeout(timer);timer = null;// 定义新的定时器,一段时间后进行操作timer = setTimeout(function() {fn.apply(this, arg);}, delay);};}let count = 0;// 主体function scrollHandle(e) {console.log(e.type, ++count); // scroll}// 代理const proxyScrollHandle = debounce(scrollHandle, 500);window.onscroll = proxyScrollHandle;
调用不能平台的接口,做规范统一返回
function apiAdapter(data) {if (data.source) {return {data: data.source,code: data.status === 'ok' ? 0 : 1}}return data}const api1 = {data: {},code: 0}const api2 = {source: {},status: 'ok'}
// 类装饰器@testableclass MyTestableClass {@logadd(a, b) {return a + b;}}function testable(target) {target.isTestable = true;}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;}MyTestableClass.isTestablenew MyTestableClass().add()
@deprecate
实现警告提示@log
打印日志装饰器@fetchInfo
上报信息装饰器定义了对象间一种一对多的依赖关系,当目标对象 Subject 的状态发生改变时,所有依赖它的对象 Observer 都会得到通知。
class Subject {constructor() {this.observers = [];}add(observer) {this.observers.push(observer);}remove(observer) {let idx = this.observers.findIndex((item) => item === observer);idx > -1 && this.observers.splice(idx, 1);}notify() {for (let o of this.observers) {o.update();}}}class Observer {constructor(name) {this.name = name;}update() {console.log(`目标者通知我更新了,我是:${this.name}`);}}// 实例化目标者let subject = new Subject();// 实例化两个观察者let obs1 = new Observer("Drex");let obs2 = new Observer("Wang");// 向目标者添加观察者subject.add(obs1);subject.add(obs2);subject.notify();
class EventBus {constructor() {this.events = {};}// 订阅事件subscribe(event, callback) {if (typeof callback !== "function") {throw "callback is not function";return;}if (!event) {throw "event is not null";return;}(this.events[event] || (this.events[event] = [])).push(callback);}// 发布事件publish(event, ...data) {if (!event) {throw "event is not null";return;}if (!this.events[event]) {return;}this.events[event].map((fn) => {fn(...data);});}// 清空事件remove(event) {// 不存在直接返回if (!event) {return;}// 事件存在即删除if (this.events[event]) {delete this.events[event];}}}const eb = new EventBus();eb.subscribe("console", (value) => console.log(1, value));eb.subscribe("console", (value) => console.log(2, value));eb.subscribe("console", (key, value, label) =>console.log(2, key, value, label));eb.publish("console", 0);eb.publish("console", 123, 456, 789);eb.remove("console");
优点
缺点
多个对象,指的不一定得是实例化的对象,也可以将其理解成互为独立的多个项。当这些项在处理时,需要知晓并通过其他项的数据来处理。
如果每个项都直接处理,程序会非常复杂,修改某个地方就得在多个项内部修改
我们将这个处理过程抽离出来,封装成中介者来处理,各项需要处理时,通知中介者即可。
参考
JavaScript 中常见的十五种设计模式ES6 设计模式使用 Javascript 原生的 Proxy 优化应用js - 观察者模式与订阅发布模式