看到 Proxy就应该想到代理模式(Proxy Pattern),php
Proxy
是 Javascript ES2015 标准的一部分,咱们应该学会使用它,代理模式是一种设计模式,使用算法
Proxy
对象能够垂手可得的在 Javascript 中建立代理模式。然而,使用设计模式并非目的,目的在于解决实际问题。本文首先会简单介绍设计模式
Proxy
的基本用法,接着将会叙述如何使用数组
Proxy
建立代理模式而且对咱们的应用进行优化。
Proxy 的基本使用 开始学习promise
Proxy
的使用以前,建议首先对 Reflect 有必定的了解,若是很陌生的话,建议先花 1 分钟浏览相关知识。
好了,如今假设已经具有了必定的缓存
Reflect
知识,就开始掌握网络
Proxy
吧。
基本语法 和app
Proxy
相关的方法一共就两个:
构造方法 本文着重讨论异步
Proxy.revocable()
建立一个可撤销的async
Proxy
对象,其他与构造函数相似,理解了
Proxy
的构造方法后,该方法与构造方法使用很是相似,本文再也不涉及
接下来本文将围绕
Proxy
的构造方法进行讲解。
let p = new Proxy(target, handler);
参数
target 任何类型的对象,包括原生数组,函数,甚至另外一个
Proxy
对象
handler 一个对象,其属性是当执行一个操做时定义代理的行为的函数, 容许的属性一共 13 种,与
Reflect
的方法名一致
返回
p
Proxy
对象
注意:
new Proxy
是稳定操做,不会对
target
有任何影响。
下面来看几个表明性的例子,便于加深理解。
代理一个对象字面量:
const target = {}; const handler = { set: (obj, prop, value) => { obj[prop] = 2 * value; }, get: (obj, prop) => { return obj[prop] * 2; } }; const p = new Proxy(target, handler); p.x = 1; // 使用了 set 方法 console.log(p.x); // 4, 使用了 get 方法
代理一个数组:
const p = new Proxy( ['Adela', 'Melyna', 'Lesley'], { get: (obj, prop) => { if (prop === 'length') return `Length is ${obj[prop]}.`; return `Hello, ${obj[prop]}!`; } } ); console.log(p.length) // Length is 3. console.log(p[0]); // Hello, Adela console.log(p[1]); // Hello, Melyna console.log(p[2]); // Hello, Lesley
代理一个普通函数:
const foo = (a, b, c) => { return a + b + c; } const pFoo = new Proxy(foo, { apply: (target, that, args) => { const grow = args.map(x => x * 2); const inter = Reflect.apply(target, that, grow); return inter * 3; } }); pFoo(1, 2, 3); // 36, (1 * 2 + 2 * 2 + 3 * 2) * 3
代理构造函数
class Bar { constructor(x) { this.x = x; } say() { console.log(`Hello, x = ${this.x}`); } } const PBar = new Proxy(Bar, { construct: (target, args) => { const obj = new Bar(args[0] * 2); return obj; } }); const p = new PBar(1); p.say(); // Hello, x = 2
Proxy
的基本用法无出其上,可
Proxy
的真正用途尚未显现出来,接下来结合设计模式中的一种模式 —— 代理模式 —— 进一步讨论。
使用 Proxy 建立代理模式 从上面的例子并不能看出
Proxy
给咱们带来了什么便利,须要实现的功能彻底能够在原函数内部进行实现。既然如此,使用代理模式的意义是什么呢?
遵循“单一职责原则”,面向对象设计中鼓励将不一样的职责分布到细粒度的对象中,
Proxy
在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念
遵循“开放-封闭原则”,代理能够随时从程序中去掉,而不用对其余部分的代码进行修改,在实际场景中,随着版本的迭代可能会有多种缘由再也不须要代理,那么就能够容易的将代理对象换成原对象的调用
达到上述两个原则有一个前提就是代理必须符合“代理和本体接口一致性”原则:代理和原对象的输入和输出必须是一致的。这样对于用户来讲,代理就是透明的,代理和原对象在不改动其余代码的条件下是能够被相互替换的。
代理模式的用途很普遍,这里咱们看一个缓存代理的例子。
首先建立一个
Proxy
的包装函数,该函数接受须要建立代理的目标函数为第一个参数,以缓存的初值为第二个参数:
const createCacheProxy = (fn, cache = new Map()) => { return new Proxy(fn, { apply(target, context, args) { const argsProp = args.join(' '); if (cache.has(argsProp)) { console.log('Using old data...'); return cache.get(argsProp); } const result = fn(...args); cache.set(argsProp, result); return result; } }); };
而后咱们使用乘法函数
mult
去建立代理并调用:
const mult = (...args) => args.reduce((a, b) => a * b); const multProxy = createCacheProxy(mult); multProxy(2, 3, 4); // 24 multProxy(2, 3, 4); // 24, 输出 Using old data
也可使用其余的函数:
const squareAddtion = (...args) => args.reduce((a, b) => a + b ** 2, 0); const squareAddtionProxy = createCacheProxy(squareAddtion); squareAddtionProxy(2, 3, 4); // 29 squareAddtionProxy(2, 3, 4); // 29, 输出 Using old data
对于上面这个例子,有三点须要注意:
对于检测是否存在旧值的过程较为粗暴,实际应用中应考虑是否应该使用更为复杂精确的判断方法,须要结合实际进行权衡;
createCacheProxy
中的
console.log
违背了前文所说的“代理和本体接口一致性”原则,只是为了开发环境更加方便性的调试,生产环境中必须去掉;
multProxy
与
squareAdditionProxy
是为了演示使用方法而在这里使用了相对简单的算法和小数据量,但在实际应用中数据量越大、
fn
的计算过程越复杂,优化效果越好,不然,优化效果不只有可能不明显反而会形成性能降低
代理模式的实际应用 这一节结合几个具体的例子来加深对代理模式的理解。
函数节流 若是想要控制函数调用的频率,可使用代理进行控制:
须要实现的基本功能:
const handler = () => console.log('Do something...'); document.addEventListener('click', handler);
接下来使用
Proxy
进行节流。
首先使用构造建立代理函数:
const createThrottleProxy = (fn, rate) => { let lastClick = Date.now() - rate; return new Proxy(fn, { apply(target, context, args) { if (Date.now() - lastClick >= rate) { fn(args); lastClick = Date.now(); } } }); };
而后只须要将原有的事件处理函数进行一曾包装便可:
const handler = () => console.log('Do something...'); const handlerProxy = createThrottleProxy(handler, 1000); document.addEventListener('click', handlerProxy);
在生产环境中已有多种工具库实现该功能,不须要咱们本身编写
图片懒加载 某些时候须要延迟加载图片,尤为要考虑网络环境恶劣以及比较重视流量的状况。这个时候可使用一个虚拟代理进行延迟加载。
首先是咱们最原始的代码:
const img = new Image(); img.src = '/some/big/size/image.jpg'; document.body.appendChild(img);
为了实现懒加载,建立虚拟图片节点
virtualImg
并构造建立代理函数:
const createImgProxy = (img, loadingImg, realImg) => { let hasLoaded = false; const virtualImg = new Image(); virtualImg.src = realImg; virtualImg.onload = () => { Reflect.set(img, 'src', realImg); hasLoaded = true; } return new Proxy(img, { get(obj, prop) { if (prop === 'src' && !hasLoaded) { return loadingImg; } return obj[prop]; } }); };
最后是将原始的图片节点替换为代理图片进行调用:
const img = new Image(); const imgProxy = createImgProxy(img, '/loading.gif', '/some/big/size/img.jpg'); document.body.appendChild(imgProxy);
异步队列 这个需求是很常见的:前一个异步操做结束后再进行下一个异步操做。这部分我使用
Promise
进行实现。
首先构造一个最为简单的异步操做
asyncFunc
:
const callback = () => console.log('Do something...'); const asyncFunc = (cb) => { setTimeout(cb, 1000); } asyncFunc(callback); asyncFunc(callback); asyncFunc(callback);
能够看到控制台的输出是 1s 以后,几乎是同时输出三个结果:
// .. 1s later .. Do something... Do something... Do something...
接下来咱们使用
Promise
实现异步队列:
const createAsyncQueueProxy = (asyncFunc) => { let promise = null; return new Proxy(asyncFunc, { apply(target, context, [cb, ...args]) { promise = Promise .resolve(promise) .then(() => new Promise(resolve => { Reflect.apply(asyncFunc, this, [() => { cb(); resolve(); }, ...args]); })); } }); };
上面这段代码经过
Promise
实现了异步函数队列,建议在理解了
Promise
以后再理解阅读上面这段代码。
上面这段代码测试经过,有两点须要注意:
promise
的值并不能肯定是否为
Promise
,须要使用
Promise.resolve
方法以后才能使用
then
方法
Reflect.apply
方法中的第三个参数是数组,形同与
Function.prototype.apply
的第二个参数
而后使用代理进行替换并调用:
const timeoutProxy = createAsyncQueueProxy(asynFunc); timeoutProxy(callback); timeoutProxy(callback); timeoutProxy(callback);
能够看到控制台的输出已经像咱们指望的那样: 前一个异步操做执行完毕以后才会进行下一个异步操做。
// .. 1s later .. Do something... // .. 1s later .. Do something... // .. 1s later .. Do something...
除了上面这种使用代理的方式实现异步队列外,在个人另外一篇博客进阶 Javascript 生成器中,还使用了另一种方式。
结语 本文首先介绍了 ES2015 中关于
Proxy
的基本用法,接着讨论了代理模式的使用特色,而后结合实际列举了几种常见的使用场景。最后列举一些比较有价值的参考资料供感兴趣的开发者继续阅读。
参考资料 Javascript Proxy Design Pattern
JavaScript设计模式与开发实践 - 曾探
JavaScript Design Patterns: Proxy
转载于猿2048:→《使用 Javascript 原生的 Proxy 优化应用》