js设计模式
邵预鸿 Lv5

构造

通过构造函数,减少对象变量声明,同时能得到对象相同的结构

1
2
3
4
5
6
7
8
9
10
function Info(name,age){
this.name = name;
this.age = age;
this.log = ()=>{
console.log(this.name,this.age)
}
}

const p = new Info('xiaoming',22);
const p2 = new Info('xiaohong',33)

原型模式

构造模式有个大的问题,log函数每次new Info时,都会声明一次,会在内存中重新分配 一块内存

实际上,log做的是同一个事,所以通过原型模式可以弥补这面的不足

这样,每一个new Info时,log函数只声明了一次

1
2
3
4
5
6
7
8
9
10
11
function Info(name,age){
this.name = name;
this.age = age;
}

Info.prototype.log = ()=>{
console.log(this.name,this.age)
}

const p = new Info('xiaoming',22);
const p2 = new Info('xiaohong',33)

类模式

原型模式可以用ES6方式简写,实现是一样的

1
2
3
4
5
6
7
8
9
class Info{
constructor(name,age){
this.name = name;
this.age = age;
}
log(){
console.log(this.name,this.age)
}
}

通过class实例的类,方法挂载到了prototype上

工厂模式

利用函数、类处理多个重复的逻辑,返回一个对象,开发者无需关心内部调用步骤

1
2
3
4
5
6
7
8
9
function stu(name,age){
const opt = {};
opt.name = name;
opt.age = age;
return opt;
}

//
stu('xiaoming',22) // { name: 'xiaoming',age: 22}

建造者模式

工厂模式不同,工厂模式由内部自己调用完成 最后输入对象。

建造者模式 让开发者了解全程调用过程,利用一个函数完成 整个步骤的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Mask{
constructor(opt){
this.defualtData = opt;
}
setData(data){
this.defaultData = data;
}
render(){
// 渲染dom
document.body.appendChild(template)
}
}

// 统一管理,用于建造
function buildFunc(newClass){
newClass.setData();
newClass.render();
}

buildFunc(new Mask());

单例设计模式

一个class类,new 多次,始终只有一个最终生成的实例,即new class() ===new class() ===new class();

单例设计模式,实现过程为,一个闭包,用一个变量记录是否已经 new实例过,如果是返回记录的实例。否则new一个实例并赋值给闭包变量返回。

ES5写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const func = (()=>{
let install = null;

class Info{
constructor(name,age){
this.name = name;
this.age = age;
}
log(){
console.log(this.name,this.age);
}
}


return function(name,age){
if(!install){
install = new Info(name,age);
}
return install;
}
})();

const p1= new func('xiaochen',25)
const p2= new func('xiaoming',215)
// p1 === p2 true
ES6写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Info{
constructor(name,age){
if(!Info.install){
this.name = name;
this.age = age;
Info.install = this;
}
return Info.install;

}
log(){
console.log(this.name,this.age);
}
}

const p1 = new Info('xiaohong',22);
const p2 = new Info('xiaoming',33);
console.log(p1,p2,p1===p2);
弹窗案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const onceMode = (()=>{
let install = null;
return function(){
if(!install){
const mask = document.createElement('div');
mask.classList.add('el-dialog');
mask.style.cssText = "width: 200px;height: 200px;background-color: #ccc;display: none";
document.body.appendChild(mask);
install = mask;
}
return install;
}
})();

function openMaskBtnBindEvent(){
const btn = document.querySelector('#openMask');
btn.addEventListener('click',()=>{
const maskDom = onceMode();
maskDom.style.display="block";
})
}


function clasMaskBtnBindEvent(){
const btn = document.querySelector('#closeMask');
btn.addEventListener('click',()=>{
const mask = document.querySelector('.el-dialog');
if(mask){
mask.style.display = "none"
}

})
}
window.addEventListener('load',function(){
openMaskBtnBindEvent();
clasMaskBtnBindEvent();
})

装饰器模式

装饰器模式主要为解决,函数扩展的问题,请看以下问题

1
2
3
4
function test(){
console.log('111111')
}
test();

现在我们希望在test执行前输出”00000”,执行后输出 “22222”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Function.prototype.before = function (beforeFunc) {
let that = this;
return function () {
beforeFunc.apply(this, arguments);
const _this = that.apply(this, arguments);
return _this;
}
}
Function.prototype.after = function (afterFunc) {
let that = this;
return function () {
const _this = that.apply(this, arguments);
afterFunc.apply(this, arguments);
return _this;
}
}
function test() {
console.log('1111111111');
return 'test func'
}

const func = test
.before(() => { console.log('000000') })
.after(() => { console.log('22222') });

func();

适配器模式

需求: 某项目需要接入两个地图页面展示,百度地图 和 高德地图 ,两个员工 一个负责百度地图 开发,一个负责高德地图开发。代码如下

接入百度地图的员工

1
2
3
4
5
6
7
8
9
10
11
class BaiduMap{
init(){
......
}
show(){
document.body.innerHTML = '......'
}
close(){
.............
}
}

接入高德地图的员工

1
2
3
4
5
6
7
8
9
10
11
class GaoDeMap{
start(){
......
}
display(){
document.body.innerHTML = '......'
}
remove(){
.............
}
}

上面两位员工 分别有自己的个人习惯,命名方式都不能,两个一起接入时,会造成不停的判断,为了解决这个问题,适配器模式的思维很有用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class GaoDeMapAdapator extends GaoDeMap{
init(){
this.start();
}
show(){
this.display();
}
close(){
this.remove();
}
}



document.querySelector('#btn').click=function(){
let map= null;
if("需要用百度地图 "){
map = new BaiduMap();
}else{
map = new GaoDeMapAdapator();
}
map.init();
map.show();
map.close()
}

**案例2 **

老项目为jqeury搭建,请求使用的$.ajax方式,现在要求将jquery改为axios写法。

以前方式: 大量删除$.ajax,改写为axios

新思维:

1
2
3
4
5
6
import axios from 'axios'
var $ = {
ajax: (params)=>{
axios.get()....
}
}

不修改$.ajax,直接在window上挂载一个变量,内部重写ajax

策略模式

抽离重复逻辑,通过定义对象引用,让代码更加简洁。

老项目

1
2
3
4
5
6
7
8
9
10
11
function computedMoney(level, base){
if(level === 1){
return base*6
}
if(level === 2){
return base*4
}
if(level === 3){
return base * 2
}
}

改进后

1
2
3
4
5
6
7
8
const computedBase = {
1: base=> base*6,
2: base=> base*4,
3: base=> base*2,
}
function computedMoney(level, base){
return computedBase[level](base);
}

代理模式Proxy

利用数据监听劫持,代理其它逻辑,vue3简单实现更新数据驱动视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
name: ''
}

window.proxy = new Proxy(obj,{
get(target,key){
return target[key]
},
set(target,key,val){
const p = document.querySelector('#main');
p.innerText = val;
target[key] = val;
}
})

观察者模式

观察者模式定义了一对多的依赖关系,通过目标对象的状态更新,通知所有依赖观察者对象自动更新.

实例

点击发布通知,消息推送到绿色的6个组件,不考虑react/vue的思想,原生实现这个问题。

通过观察者模式,可以直接由目标对象更新,让依赖观察者自动更新

http://server.yuhongshao.cn/static/yuhongshao/20230512180037.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Main{
constructor(){
this.domList = [];
}
add(elClass){
this.domList.push(elClass);
}
update(msg){
this.domList.forEach(item=>item.update(msg))
}
}

class Observe{
constructor(el){
this.dom = document.querySelector(el);
}
update(msg){
this.dom.innerHTML = msg;
}
}

// 将所有观察者加入统一管理
const DomList = new Main();
DomList.add(new Observe('#bar1'));
DomList.add(new Observe('#bar2'));
DomList.add(new Observe('#bar3'));
DomList.add(new Observe('#bar4'));
DomList.add(new Observe('#bar5'));
DomList.add(new Observe('#bar6'));


document.querySelector("#search button").onclick=function(){
const input = document.querySelector('#search input');
const inputValue = input.value;
if(inputValue){
DomList.update(inputValue);
}

}

发布与订阅模式

观察者模式不能对事件进行精细化管理,比如,我希望自己手写函数,通过Main类最后统一调度。为了解决这个问题,有了发布与订阅模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const Main = {
events: {},
add(type, func) {
if (this.events[type]) {
this.events[type].push(func);
} else {
this.events[type] = [func];
}
},
del(type, func) {
if (!func) {
this.events[type] && (this.events[type].length = 0);
return;
}
if (this.events[type]) {
this.events[type] = this.events[type].filter(item => item !== func);
}
},
publish(type,data) {
this.events[type].forEach(item => item(data));
}
};

// 修改div innerHTML
function updateInnerHTML({msg}){
document.querySelector('.container main').innerHTML = msg;
}

function setStyle(){
document.querySelector('.container main').style.color="red";
}

window.onload=function(){
Main.add('updateInnerHTML', updateInnerHTML);
const lis = document.querySelectorAll('.container li');
console.log(lis);
lis.forEach(item=>{
item.onclick=function(){
const text = this.innerHTML;
Main.publish('updateInnerHTML', { msg: text});
}
})
}

模块模式

除了ES6模块化export imort语法,可以使用ES5类 闭包自执行函数s

1
2
3
4
5
6
var Main = (function() {
var name = '', age = 22;
return function(){
return { name,age, func}
}
})();
  • 本文标题:js设计模式
  • 本文作者:邵预鸿
  • 创建时间:2021-06-23 17:35:17
  • 本文链接:/images/logo.jpg2021/06/23/js设计模式/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!