前言
这里花几分钟时间来讲解几个设计模式以及它们的应用场景。可以帮助你在平时开发时把设计模式用起来
- 装饰器模式
- 迭代器模式
- 发布订阅模式
- 工厂模式
- 单例模式
- 代理模式
- 外观模式
废话不多说,直接上demo
装饰器模式
装饰器DEMO
<!--index.js-->
function decoratorHoc(target){
target.customerName = 'xuqiang';
}
@decoratorHoc
class Person{}
console.log(Person.customerName);
复制代码
装饰器疑问?️
- decoratorHoc是个函数,为何支持@decoratorHoc?不要把这个当作理所当然。可以试下把代码拷入vscode,然后code runner,发现报了这段错
那么下面三步来把babel环境搞一下
- 安装全局babel
<!--在当前index.js目录下-->
yarn add babel-cli -g
yarn add babel-plugin-transform-decorators-legacy
复制代码
- 配置.babelrc
<!--在当前index.js目录下新建.babelrc文件,内容如下-->
{
plugins: ['transform-decorators-legacy']
}
复制代码
- 编译index.js
<!--在当前index.js目录下-->
babel index.js --out-file compiled.js
复制代码
这里把index.js编译为了compiled.js。然后code runner compiled.js,发现日志输出了"xuqiang"
至此你就实现了装饰器decoratorHoc装饰器模式应用场景
装饰器模式可以说是非常方便地扩展了一个对象。那么平时开发中是不是有几类代码跟装饰器模式很相似
- react的高阶组件
- dva的@connect
这里就举两个例子,更多场景需要你自己去发现了
迭代器模式
迭代一个对象,就可以称为是迭代器,下面从es6的迭代器来讲解下
for-of遍历json
这是一个es6的自定义迭代器DEMO,讲解如何写一个迭代器来遍历普通对象,因为遍历数组内置已经支持了
let obj = {
name: 'xuqiang',
age: 20,
[Symbol.iterator]: () => {
let props = Object.keys(obj), i = props.length;
return {
next(){
if(i > 0){
i = i - 1;
return {
value: obj[props[props.length - i - 1]],
done: false
};
}else{
return {
value: '',
done: true
};
}
}
};
}
}
for(let prop of obj){
console.log(prop);
}
复制代码
code runner发现可以正确遍历一个Object了
发布订阅模式
发布订阅的场景
用一个老生常谈的场景先来解释下发布订阅,那就是学生时代的订牛奶,先来整理下需求
- 有一个工厂,有一个let products = {},有一种属性叫做牛奶。此属性对应一个数组,数组存储每个订奶牛人的数据。
- 工厂有一个定时器,每天早上8点开始遍历products,进行牛奶派发
- 有很多消费者,向工厂订牛奶
需求整理清楚了,下面用个DEMO来实现下
// 发布订阅模式
// 发布订阅模式
class Puber{
constructor(name){
this.name = name;
this.products = {};
this.startInterval();
}
startInterval(){
setInterval(() => {
for(let prop in this.products){
if(this.products.hasOwnProperty(prop)){
this.emit(prop);
}
}
}, 3000);
}
sub(evt, cb){
if(!this.products[evt]){
this.products[evt] = [];
}
this.products[evt].push(cb);
}
emit(evt){
if(this.products[evt]){
let cbs = this.products[evt];
for(let cb of cbs){
cb && cb();
}
}
}
}
class Suber{
constructor(name){
this.name = name;
this.puber = new Puber('徐强');
}
getPuber(){
console.log(`我是${this.name},我的牛奶配送员是${this.puber.name}`);
}
sub(evt, cb){
this.puber.sub(evt, cb);
}
}
let customer1 = new Suber('刘思琪');
customer1.getPuber();
customer1.sub('牛奶', () => {
console.log(`现在时间是${+new Date},牛奶送至未来科技城,收件人:刘思琪`);
});
let customer2 = new Suber('徐大佬');
customer2.getPuber();
customer2.sub('牛奶', () => {
console.log(`现在时间是${+new Date},牛奶送至空港新城,收件人:徐大佬`);
});
复制代码
发现刘思琪和徐大佬,已经成功订了牛奶,并且每天早上10点钟开始派发牛奶了。当然了,这里没有写取消订牛奶的操作,大家可以自己接着写
工厂模式
class jQuery{
constructor(name){
this.name = name;
}
}
function $(name){
return new jQuery(name);
}
let obj1 = $('xuqiang');
let obj2 = $('liusiqi');
console.log(obj1);
console.log(obj2);
复制代码
工厂模式总结
这里举例也是用的jquery,可以发现jquery的$函数就是用了工厂模式,那么工厂模式有哪些好处呢?
- 不需要自己调用new jQuery。直接用$很方便
- 类似React.createElement,屏蔽了开发者直接使用new VNode,符合开放封闭原则,VNode的实现对开发者不可见
单例模式
光是单例模式的话比较简单,感觉没什么亮点,所以这里结合装饰器模式来写一个单例模式
写一个装饰器模式+单例模式的骚操作
function getInstanceHoc(target){
target.getInstance = (() => {
let instance;
return () => {
if(!instance){
instance = new Function(`return new ${target}()`)();
}
return instance;
}
})();
}
@getInstanceHoc
class Person{
eat(){
console.log('i am eating');
}
}
let obj = Person.getInstance();
console.log(obj);
obj.eat();
let obj1 = Person.getInstance();
console.log(obj === obj1);
复制代码
code runner之后发现,obj和obj1都可以eat了,然后比较obj===obj1,发现是true,说明单例模式已经成功了
代理模式
用es6的Proxy来讲一下代理
class Vue{
constructor(data){
let _data = data;
return new Proxy(this, {
get(target, key){
return _data[key];
},
set(target, key, val){
_data[key] = val;
return true;
}
});
}
}
let obj = new Vue({
name: '徐强',
age: 20
});
console.log(obj.name);
obj.name = '刘思琪';
console.log(obj.name);
复制代码
外观模式
外观模式在jquery中很常见。在我们平时开发中感觉也很好用
function winAlert(title, message, buttons, cb){
if(cb === undefined){
cb = buttons;
buttons = null;
}
console.log(title);
console.log(message);
console.log(buttons);
console.log(cb);
}
winAlert('提示', '操作完成', ['确认', '取消'], () => {});
winAlert('提示', '操作完成', () => {});
复制代码
运行结果:
这让我想起目前业务中后端所有接口都是app/htmlGateway.do,只是version,rd等参数区别,从某种角度来说这是符合外观模式的。封闭后端api的内部实现,开放htmlGetway给前端调用
但是目前这种方式有个问题是,对于yapi,rap2等mock平台不太友好。因为api路径都是一致的,会不太好mock,也可能是因为我没有发现解决方案吧,所以觉得不太好mock
ok 这几种设计模式的demo都已经讲完了。有问题可以一起谈论。