一、装饰器模式-适用于灵活添加功能模块(场景:点击按钮打开弹窗,并修改该按钮禁用和文字内容)
(1)、ES6实现方式
// 定义按钮
class OpenButton {
onClick() {
const dialog = new Dialog()
dialog.visible = true
}
}
// 定义按钮装饰器
class Decorator {
constructor(button) {
this.button = button
}
// 按钮点击
onClick() {
this.button.onClick()
this.disabledButton()
this.changeButtonText()
}
disabledButton() {
const btn = getElementById('#btn')
btn.setAttribute('disabled', true)
}
changeButtonText() {
const btn = getElementById('#btn')
btn.innerText('按钮文字已修改')
}
changexx() {
}
}
// 创建按钮
const btn = new OpenButton()
const decorator = new Decorator(btn)
// 绑定点击事件
document.getElementById('#btn').addListener('click', () => {
decorator.onClick()
})
*思考:单一职责原则:将不同的职责分离,可以做到每个职责都能被灵活地复用;不同职责之间无法相互干扰,不会出现因为修改了 A 逻辑而影响了 B 逻辑
(2)、ES7 @语法糖
// 1、装饰类
function classDecorator(target) {
// 装饰器第一个参数target是目标类
target.hasDecorator = true
return target
}
@classDecorator
class Button {
}
console.log('Button被装饰了', Button.hasDecorator) // 'Button被装饰了', true
// 2、装饰方法
function funcDecorator(target, name, descriptor) {
// 装饰器第三个参数指向解释器
let originalMethod = descriptor.value
descriptor.value = function() {
console.log('修饰后的')
return originalMethod.apply(this, arguments)
}
retturn descriptor
}
class Button{
@funcDecorator
onClick() {
console.log('原始的')
}
}
const button = new Button()
button.onClick() // 修饰后的 原始的
二、适配器模式-适用于同时兼容多种条件(场景:axios适配的兼容方案)
(1)、axios常用的三种调用方式
axios.get('/user')
.then(() => {})
.catch(() => {})
axios.post('/user', {name:'xxx'})
.then(() => {})
.catch(() => {})
axios({
method: 'post',
url: '/user',
data: {}
})
(2) axios适配各种调用形式、适配xhr和nodejs的实现源码
// 若用户未手动配置适配器,则使用默认的适配器
var adapter = config.adapter || defaults.adapter;
// dispatchRequest方法的末尾调用的是适配器方法
return adapter(config).then(function onAdapterResolution(response) {
// 请求成功的回调
throwIfCancellationRequested(config);
// 转换响应体
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
// 请求失败的回调
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// 转换响应体
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
// axios/lib/default.js中默认adapter适配器逻辑
function getDefaultAdapter() {
var adapter;
// 判断当前是否是node环境
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// 如果是node环境,调用node专属的http适配器
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
// 如果是浏览器环境,调用基于xhr的适配器
adapter = require('./adapters/xhr');
}
return adapter;
}
*思考:一个好的适配器:把变化留给自己,把统一留给用户。axios中,所有关于 http 模块、关于 xhr 的实现细节,全部被 Adapter 封装进了自己复杂的底层逻辑里,暴露给用户的都是十分简单的统一的东西——统一的接口,统一的入参,统一的出参,统一的规则。
三、代理模式-在行为与目标之前再加一层处理手段(场景:事件、虚拟、缓存、保护)
(1)、事件代理: 点击任何一个li标签,都能开启一个弹窗
<ul id="wrapper">
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ul>
// 常规思维:遍历每一个li标签,并绑定事件
// 利用事件代理只在父元素上绑定事件,并判定事件target是否是li标签
document.getElementById('wrapper').addEventListener('click', (e) => {
if(e.target.tagName === 'li') {
// 以下是监听函数的函数体
e.preventDefault()
alert(`我是${e.target.innerText}`)
}
})
(2)、虚拟代理:图片预加载,先用站位图站位,请求到实际图片资源时再替换img的src
class PreLoadImage{
constructor(imageNode) {
// 获取真实的image
this.imageNode = imageNode
}
// 修改image的src属性值
setSrc(imageUrl) {
this.imageNode.src = imageUrl
}
}
class ProxyImage {
static LOADING_URL = 'xx'
constructor(tagetImage) {
// 真实的image,PreLoadImage实例
this.tagetImage = tagetImage
}
// 操作虚拟image完成图片资源的加载
setSrc() {
// 真实img节点初始化时展示的是一个占位图
this.targetImage.setSrc(ProxyImage.LOADING_URL)
// 创建一个帮我们加载图片的虚拟Image实例
const virtualImage = new Image()
// 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
virtualImage.onload = () => {
this.targetImage.setSrc(targetUrl)
}
// 设置src属性,虚拟Image实例开始加载图片
virtualImage.src = targetUrl
}
}
(3)、缓存代理:计算量大时缓存数据结果,再次调用时使用缓存
// addAll方法:对传入的所有参数做求和
const addAll = function() {
console.log('进行了一次新计算')
let result = 0
const len = arguments.length
for(let i = 0; i < len; i++) {
result += arguments[i]
}
return result
}
// 为求和方法创建代理
const proxyAddAll = (function(){
// 求和结果的缓存池
const resultCache = {}
return function() {
// 将入参转化为一个唯一的入参字符串
const args = Array.prototype.join.call(arguments, ',')
// 检查本次入参是否有对应的计算结果
if(args in resultCache) {
// 如果有,则返回缓存池里现成的结果
return resultCache[args]
}
return resultCache[args] = addAll(...arguments)
}
})()
proxyAddAll(1,2,3) // 6
proxyAddAll(1,2,3) // 6
(4)、保护代理:利用es6中的proxy实现在数据操作(get、set)过程中的保护
// obj是代理的目标对象;handler对象用来定义保护行为,当操作obj时handler会进行拦截
const proxy = new Proxy(obj, handler)
场景:交友网站访问网友的权限校验
const baseInfo = ['name']
const privateInfo = ['age', 'avater']
const present = {
type: '游艇',
value: 60,
}
const girl = {
name: '艾美丽',
age: 18,
avater: '空姐',
// 礼物数组
presents: [],
// 拒收50块以下的礼物
bottomValue: 50,
// 记录最近一次收到的礼物
lastPresent: present,
}
const guest = {
name: '小强',
isVip: false,
isValidated: false
}
const interceptor = new Proxy(girl, {
get: function (girl, key) {
if(baseInfo.indexOf(key)!==-1 && !user.isValidated) {
alert('您还没有完成验证')
return
}
// 此处我们认为只有验证过的用户才可以购买VIP
if(user.isValidated && privateInfo.indexOf(key) && !user.isVip) {
alert('只有VIP才可以查看该信息哦')
return
}
},
set: function(girl, key, value) {
// 最近一次送来的礼物会尝试赋值给lastPresent字段
if(key === 'lastPresent') {
if(val.value < girl.bottomValue) {
alert('sorry,您的礼物被拒收了')
return
}
// 如果没有拒收,则赋值成功,同时并入presents数组
girl.lastPresent = val
girl.presents = [...girl.presents, val]
}
}
})
// proxy的处理器类型汇总
// 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
handler.getPrototypeOf()
// 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
handler.setPrototypeOf()
// 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
handler.isExtensible()
// 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
handler.preventExtensions()
// 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
handler.getOwnPropertyDescriptor()
// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
andler.defineProperty()
// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.has()
// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.get()
// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.set()
// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.deleteProperty()
// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.ownKeys()
// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.apply()
// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
handler.construct()