前言

本篇主要是读 《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 = "棉花"

实际开发中最常见的四种代理类型:事件代理、虚拟代理、缓存代理和保护代理。

不展开记录了,本质都是写多一个方法,这个方法又做一些额外的事情,比如过滤或校验等,从这个方法中间接的拿到我们想要的东西。