JS 设计模式-工作常用的
单例模式
// 单例构造函数
function CreateSingleton (name) {
this.name = name;
this.getName();
};
// 获取实例的名字
CreateSingleton.prototype.getName = function() {
console.log(this.name)
};
// 单例对象
var Singleton = (function(){
var instance;
return function (name) {
if(!instance) {
instance = new CreateSingleton(name);
}
return instance;
}
})();
// 创建实例对象1
var a = new Singleton('a');
// 创建实例对象2
var b = new Singleton('b');
console.log(a===b);
工厂模式
//User类
class User {
//构造器
constructor(opt) {
this.name = opt.name;
this.viewPage = opt.viewPage;
}
//静态方法
static getInstance(role) {
switch (role) {
case 'superAdmin':
return new User({ name: '超级管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据', '权限管理'] });
break;
case 'admin':
return new User({ name: '管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据'] });
break;
case 'user':
return new User({ name: '普通用户', viewPage: ['首页', '通讯录', '发现页'] });
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
}
//调用
let superAdmin = User.getInstance('superAdmin');
let admin = User.getInstance('admin');
let normalUser = User.getInstance('user');
发布/订阅者模式
手写 EventEmitter
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, func) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(func);
}
emit(eventName, ...params) {
const events = this.events[eventName];
if (events) {
events.forEach(event => {
event.apply(this, params)
})
}
}
once(eventName, func) {
const cb = () => {
func();
this.off(eventName, func);
}
this.on(eventName, cb);
}
off(eventName, func) {
if (this.events[eventName]) {
if (!func) {
this.events[eventName] = []
} else {
this.events[eventName].splice(this.events[eventName].indexOf(func), 1)
}
}
}
}
装饰器模式
写 react 的时候经常会遇到,本质上它就是个包装函数,主要用于多个不同类之间共享或者扩展一些方法或者行为。
function isAnimal(target) {
target.isAnimal = true;
return target;
}
@isAnimal
class Cat {
...
}
console.log(Cat.isAnimal); // true
值得一提的是,装饰器居然还在 tc39 的 stage-2 阶段。
所以要用的话需要加 babel-plugin-transform-decorators-legacy 插件
babel --plugins transform-decorators-legacy es6.js > es5.js
策略模式
常用于避免写很多 if 条件语句的情况,打个比方算绩效奖金的函数
function calcSalary(salary, score) {
if (score === '4') {
return salary * 6
} else if (score === '3.75') {
return salary * 4
} else if (score === '3.5+') {
return salary * 3
} else if (score === '3.5') {
return salary * 2
} else if (score === '3.5-') {
return salary * 1
} else if (score === '3.25') {
return 0;
}
}
这是阿里系的公司可能的计算年终奖的规则,那么某天想增加一个等级叫 3.75+
,那又得往上面加 if 判断。就不希望这么做。所以可以使用策略模式优化
var defaultMap = {
'4': function (salary) { return salary * 6 },
'3.75': function (salary) { return salary * 4 },
'3.5+': function (salary) { return salary * 3 },
'3.5': function (salary) { return salary * 2 },
'3.5-': function (salary) { return salary * 1 },
'3.25': function (salary) { return 0 },
};
function calcSalary(salary, score, extendMap) {
let map = Object.assign({}, defaultMap, extendMap);
return map[score](salary);
}
calcSalary(10000, '3.75+', {
'3.75+': function (salary) { return salary * 5 }
})
用策略模式也可以简单封装表单,举个例子,你可以在代码中去循环这个结构输出 DOM,当然这样做又会遇到其他问题另说。
var formStruct = [
{
id: 1,
type: 'input',
rule: /\d+/,
placeholder: 'input something'
},
{
id: 2,
type: 'select',
require: true,
options: []
},
........
]
适配器模式
适配器模式就相当于一个转换接口,大家想想我们手机充电器通常是二岔口的,但是电源只有三岔口的。这时候就需要一个适配器把三岔口的转换成二岔口的。
它的作用其实就是解决两个软件实体间的接口不兼容问题,使用之后就可以一起工作了。
var googleMap = {
show: function () {
console.log('googleMap show!');
},
};
var baiduMap = {
show: function () {
console.log('baiduMap show!');
},
};
var renderMap = function (map) {
if (map.show instanceof Function) {
map.show();
}
};
renderMap(googleMap);
renderMap(baiduMap);
上面这段程序能够运行是因为百度地图和谷歌地图用的同一种show方法,但是我们在不知道对方使用的函数接口的时候,我们就不能这样用了(可能百度是使用了display方法来显示)。下面的baiduMapAdapter就是我们使用的适配器。
var googleMap = {
show: function () {
console.log('googleMap show!');
},
};
var baiduMap = {
display: function () {
console.log('baiduMap show!');
},
};
var renderMap = function (map) {
if (map.show instanceof Function) {
map.show();
}
};
var baiduMapAdapter = {
show: function () {
return baiduMap.display();
},
};
renderMap(googleMap);
renderMap(baiduMapAdapter);
代理模式
很久以前前端去绑定事件时,还很流行一个术语叫“事件委托”,就借用这个思想。
点击 li 标签显示它的内容,但是直接对 li 遍历添加事件对 JS 性能不太好,故而可以把事件监听设置在父元素 ul 上。
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
if (event.target.tagName === 'li') {
}
})
</script>
迭代器模式
迭代器就是提供一种可以顺序访问的模式,forEach 就是一种常用的迭代器
var forEach = function (arr, callback) {
for (var i = 0, l = arr.length; i < l; i++) {
callback.call(arr[i], i, arr[i])
}
}
// callback 顺序访问数组里的内容
forEach([1, 2, 3], function (item, n) {
console.log([item, n]);
})
其他
等用到了再补充吧