- 重要: 代理模式、装饰器模式、适配器模式
 - 不重要: 桥接模式、外观模式、组合模式、享元模式
装饰器模式
装饰器模式,又名装饰者模式。它的定义是“在不改变原对象的基础上,通过对其进行包装拓展,使原有对象可以满足用户的更复杂需求”。
 
// 定义打开按钮class OpenButton {// 点击后展示弹框(旧逻辑)onClick() {const modal = new Modal()modal.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()})
ES7,需要babel转换
// 具体的参数意义,在下个小节,这里大家先感知一下操作function funcDecorator(target, name, descriptor) {let originalMethod = descriptor.valuedescriptor.value = function() {console.log('我是Func的装饰器逻辑')return originalMethod.apply(this, arguments)}return descriptor}class Button {@funcDecoratoronClick() {console.log('我是Func的原有逻辑')}}// 验证装饰器是否生效const button = new Button()button.onClick()
适配器模式
适配器模式通过把一个类的接口变换成客户端所期待的另一种接口,可以帮我们解决不兼容的问题```
// Ajax适配器函数,入参与旧接口保持一致async function AjaxAdapter(type, url, data, success, failed) {const type = type.toUpperCase()let resulttry {// 实际的请求全部由新接口发起if(type === 'GET') {result = await HttpUtils.get(url) || {}} else if(type === 'POST') {result = await HttpUtils.post(url, data) || {}}// 假设请求成功对应的状态码是1result.statusCode === 1 && success ? success(result) : failed(result.statusCode)} catch(error) {// 捕捉网络错误if(failed){failed(error.statusCode);}}}// 用适配器适配旧的Ajax方法async function Ajax(type, url, data, success, failed) {await AjaxAdapter(type, url, data, success, failed)}
⭐代理模式
在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式。
应用
事件委托代理
- 事件捕获指的是从document到触发事件的那个节点,即自上而下的去触发事件
 - 事件冒泡是自下而上的去触发事件
 - 绑定事件方法的第三个参数,就是控制事件触发顺序是否为事件捕获。true为事件捕获;false为事件冒泡,默认false。
 

<body><ul id="list"><li>1</li><li>2</li><li>3</li></ul><script>let list = document.querySelector('#list');list.addEventListener('click',event=>{alert(event.target.innerHTML);});</script></body>
图片预加载
预加载主要是为了避免网络不好、或者图片太大时,页面长时间给用户留白的尴尬。
常见的操作是先让这个 img 标签展示一个占位图,然后创建一个 Image 实例,让这个 Image 实例的 src 指向真实的目标图片地址、观察该 Image 实例的加载情况 —— 当其对应的真实图片加载完毕后,即已经有了该图片的缓存内容,再将 DOM 上的 img 元素的 src 指向真实的目标图片地址。
此时我们直接去取了目标图片的缓存,所以展示速度会非常快,从占位图到目标图片的时间差会非常小、小到用户注意不到,这样体验就会非常好了。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>Document</title><style>.bg-container {width: 600px;height: 400px;margin: 100px auto;}.bg-container #bg-image {width: 100%;height: 100%;}</style></head><body><div id="background"><buttondata-src="https://images.pexels.com/photos/258112/pexels-photo-258112.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260">背景1</button><buttondata-src="https://images.pexels.com/photos/1025469/pexels-photo-1025469.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260">背景2</button></div><div class="bg-container"><img id="bg-image" src="/img1.jpg" /></div><script>class PreLoadImage {constructor() {this.bgImage = document.querySelector("#bg-image");}setSrc(src) {this.bgImage.src = src;}}class ProxyImage {static LOADING_URL = `loading.gif`;constructor() {this.targetImage = new PreLoadImage();}setSrc(src) {this.targetImage.setSrc(ProxyImage.LOADING_URL);let virtualImage = new Image();// 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的urlvirtualImage .onload = () => {this.targetImage.setSrc(src);};virtualImage .src = src;}}let proxyImage = new ProxyImage();let container = document.querySelector("#background");container.addEventListener("click", function (event) {let src = event.target.dataset.src;proxyImage.setSrc(src);});</script></body></html>
缓存代理
缓存代理比较好理解,它应用于一些计算量较大的场景里。在这种场景下,我们需要“用空间换时间”——当我们需要用到某个已经计算过的值的时候,不想再耗时进行二次计算,而是希望能从内存里去取出现成的计算结果。这种场景下,就需要一个代理来帮我们在进行计算的同时,进行计算结果的缓存了。
一个比较典型的例子,是对传入的参数进行求和:
// addAll方法会对你传入的所有参数做求和操作const addAll = function() {console.log('进行了一次新计算')let result = 0const len = arguments.lengthfor(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)}})()
Proxy
- Proxy 可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
 - Proxy 这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器
let wang={name: 'wanglaoshi',age: 29,height:165}let wangMama=new Proxy(wang,{get(target,key) {if (key == 'age') {return wang.age-1;} else if (key == 'height') {return wang.height-5;}return target[key];},set(target,key,val) {if (key == 'boyfriend') {let boyfriend=val;if (boyfriend.age>40) {throw new Error('太老');} else if (boyfriend.salary<20000) {throw new Error('太穷');} else {target[key]=val;return true;}}}});console.log(wangMama.age);console.log(wangMama.height);wangMama.boyfriend={age: 41,salary:3000}
- Vue2 中的变化侦测实现对 Object 及 Array 分别进行了不同的处理,Object 使用了 Object.defineProperty API,Array使用了拦截器对 Array 原型上的能够改变数据的方法进行拦截。虽然也实现了数据的变化侦测,但存在很多局限 ,比如对象新增属性无法被侦测,以及通过数组下边修改数组内容,也因此在 Vue2 中经常会使用到 $set 这个方法对数据修改,以保证依赖更新。
 - Vue3 中使用了 es6 的 Proxy API对数据代理,没有像 Vue2 中对原数据进行修改,只是加了代理包装,因此首先性能上会有所改善。其次解决了 Vue2 中变化侦测的局限性,可以不使用 $set 新增的对象属性及通过下标修改数组都能被侦测到。
 
 
