前言
本篇主要是读 《JavaScript 设计模式核⼼原理与应⽤实践》的笔记记录。
JS设计模式
装饰器模式
装饰器模式,又名装饰者模式。它的定义是“在不改变原对象的基础上,通过对其进行包装拓展,使原有对象可以满足用户的更复杂需求”。
例:
// 定义一个弹窗model
const Model = (function() {
let model = null
return function() {
if(!model) {
model = document.createElement('div')
model.innerHTML = '您还未登录哦~'
model.id = 'model'
model.style.display = 'none'
document.body.appendChild(model)
}
return model
}
})()
// 定义打开按钮
class OpenButton {
// 点击后展示弹框
onClick() {
const model = new Model()
model.style.display = 'block'
}
}
// 定义按钮对应的装饰器
class Decorator {
// 将按钮实例传入
constructor(open_button) {
this.open_button = open_button
}
onClick() {
this.open_button.onClick()
// “包装”了一层新逻辑
this.changeButtonStatus()
}
changeButtonStatus() {
this.changeButtonText()
this.disableButton()
}
disableButton() {
const btn = document.getElementById('open')
btn.setAttribute("disabled", true)
}
changeButtonText() {
const btn = document.getElementById('open')
btn.innerText = '快去登录'
}
}
const openButton = new OpenButton()
const decorator = new Decorator(openButton)
document.getElementById('open').addEventListener('click', function() {
// openButton.onClick()
// 此处可以分别尝试两个实例的onClick方法,验证装饰器是否生效
decorator.onClick()
})
适配器模式
适配器模式通过把一个类的接口变换成客户端所期待的另一种接口,可以帮我们解决不兼容的问题。
一个简单的例子,前端开发一个大表单页面时,已经定义好了各个字段的key, 后来接口出来了,发现后端定义的key和前端对不上,这时就需要一个适配器了。
axios.post('/xxx', data)
.then(function (response) {
console.log(response);
let adapteeRes = new adaptor(response); // 让适配器返回我们要的结果
console.log(adapteeRes);
})
.catch(function (error) {
console.log(error);
});
function adaptor(data){
return {
userId: data.uid,
...
}
}
axios的适配器
axios使用时有多种写法,除了在浏览器中使用,在Node环境下也能使用
axios.get()
axios.post()
axios({method: 'post',url: 'xxx', data: {}})
axios 完美地抹平了两种环境下api的调用差异,这正是对适配器模式的灵活运用。
代理模式
在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式。
在 ES6 中,提供了专门以代理角色出现的代理器 —— Proxy
const proxy = new Proxy(obj, handler)
例,网盘系统中某用户分享的文件只有vip才可以看到链接:
// 当前登陆用户信息
const user = {
// ...(一些必要的个人信息)
nickName: "好好学习",
isVIP: false,
vipLv: 2, // 几级vip
}
// 假设一个从后端获取到的文件数据结构
const file = {
fileName: "好康的内容.txt",
shareDate: "2022-01-01 00:00:00",
needVip: 3, // 需要vip几级,0表示不需要vip
downUrl: "htpp://xxxxxxx", // 下载链接
// 收到的礼物数组
presents: []
}
const shareFile = new Proxy(file, {
get: function(file, key) {
// 验证是否需要VIP
if(key == "downUrl" && file.needVip>0) {
if(!user.isVIP) {
alert('此资源需要VIP才可以查看')
return
}else if(user.vipLv < file.needVip) {
alert('您的VIP等级不够')
return
}
}
//...(此处省略其它有的没的各种校验逻辑)
}
})
// 使用,调用downUrl键时会触发验证
shareFile.downUrl
除了getter 层面的拦截还可以作 setter 层面的拦截。
还是上面的例子,礼物打赏功能校验vip
const shareFile = new Proxy(file, {
get: function(file, key) {
// 验证是否需要VIP
if(key == "downUrl" && file.needVip>0) {
if(!user.isVIP) {
alert('此资源需要VIP才可以查看')
return
}else if(user.vipLv < file.needVip) {
alert('您的VIP等级不够')
return
}
}
//...(此处省略其它有的没的各种校验逻辑)
},
set: function(file, key, val) {
if(key === 'presents') {
if(file.needVip>0) {
if(!user.isVIP){
alert('sorry,该资源为vip资源,您的礼物被拒收了')
return
}
}
// 新礼物记录
file.presents = [...file.presents, val]
}
}
})
// 调用
shareFile.presents = "棉花"
实际开发中最常见的四种代理类型:事件代理、虚拟代理、缓存代理和保护代理。
不展开记录了,本质都是写多一个方法,这个方法又做一些额外的事情,比如过滤或校验等,从这个方法中间接的拿到我们想要的东西。