本文共 8652 字,大约阅读时间需要 28 分钟。
Proxy
,你可以将一只猫伪装成一只老虎。下面大约有6个例子,我希望它们能让你相信,Proxy 提供了强大的 Javascript 元编程。 Proxy
有许多用途,包括运算符重载,对象模拟,简洁而灵活的API创建,对象变化事件,甚至Vue 3背后的内部响应系统提供动力。 Proxy
用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。 Proxy
构造函数,用来生成 Proxy
实例。 var proxy = new Proxy(target, handler);
Proxy
对象的所有用法,都是上面的这种形式。不同的只是 handle
参数的写法。其中 new Proxy
用来生成 Proxy
实例, target
是表示所要拦截的对象, handle
是用来定制拦截行为的对象。 get
陷阱,总是返回 42
。 let target = { x: 10, y: 20 }; let hanler = { get: (obj, prop) => 42 }; target = new Proxy(target, hanler); target.x; //42 target.y; //42 target.x; // 42
target.x
, target['x']
, Reflect.get(target, 'x')
等。 handler.get
handler.set
handler.has
handler.apply
handler.construct
handler.ownKeys
handler.deleteProperty
handler.defineProperty
handler.isExtensible
handler.preventExtensions
handler.getPrototypeOf
handler.setPrototypeOf
handler.getOwnPropertyDescriptor
undefined
。但 Proxy 可以改变这种情况。 const withZeroValue = (target, zeroValue) => new Proxy(target, { get: (obj, prop) => (prop in obj ? obj[prop] : zeroValue) });
withZeroValue
用来包装目标对象。如果设置了属性,则返回属性值。否则,它返回一个默认的 “零值”。 withZeroValue
,以Boolean ( false
), Number ( 0
), String ( ""
), Object ( {}
),Array ( []
)等对应的零值,则可能是隐含的。 let pos = { x: 4, y: 19 }; console.log(pos.x, pos.y, pos.z); // 4, 19, undefined pos = withZeroValue(pos, 0); console.log(pos.z, pos.y, pos.z); // 4, 19, 0
z
默认为 0
而不是 undefined
,这可能是有意义的。 Array.lastItem
来获取和设置最后一个元素。 arr[-1]
替代 arr[arr.length-1]
访问最后一个元素。 const negativeArray = els => new Proxy(els, { get: (target, propKey, receiver) => Reflect.get( target, +propKey < 0 ? String(target.length + +propKey) : propKey, receiver ) });
Numbers
,这样就可以使用一元加运算符简洁地完成。 [-1]
访问最后一个元素, [-2]
访问倒数第二个元素,以此类推。 const unicorn = negativeArray(["?", "?", "?"]); unicorn[-1]; // '?'
Symbol
最初是为了启用私有属性而引入的,但后来使用像 Object.getOwnPropertySymbols
这样的反射方法进行了淡化,这使得它们可以被公开发现。 _
,有效地标记它们“不要访问”。 Prox
提供了一种稍微更好的方法来屏蔽这些属性。 const hide = (target, prefix = "_") => new Proxy(target, { has: (obj, prop) => !prop.startsWith(prefix) && prop in obj, ownKeys: obj => Reflect.ownKeys(obj).filter( prop => typeof prop !== "string" || !prop.startsWith(prefix) ), get: (obj, prop, rec) => (prop in rec ? obj[prop] : undefined) });
hide
函数包装目标对象,并使得从 in
运算符和 Object.getOwnPropertyNames
等方法无法访问带有下划线的属性。 let userData = hide({ firstName: "Tom", mediumHandle: "@tbarrasso", _favoriteRapper: "Drake" }); userData._favoriteRapper( // undefined "_favoriteRapper" in userData ); // false
deleteProperty
和 defineProperty
之类的陷阱。除了闭包之外,这可能是最接近真正私有属性的方法,因为它们无法通过枚举,克隆,访问或修改来访问。 Proxy
启用了一种新方法:根据需要将对象包装为无效(和重新同步)属性。所有访问属性的尝试都首先检查缓存策略,该策略决定返回当前在内存中的内容还是采取其他一些操作。 const ephemeral = (target, ttl = 60) => { const CREATED_AT = Date.now(); const isExpired = () => Date.now() - CREATED_AT > ttl * 1000; return new Proxy(target, { get: (obj, prop) => (isExpired() ? undefined : Reflect.get(obj, prop)) }); };
let bankAccount = ephemeral( { balance: 14.93 }, 10 ); console.log(bankAccount.balance); // 14.93 setTimeout(() => { console.log(bankAccount.balance); // undefined }, 10 * 1000);
Csaba Hellinge 关于[代理用例][23]和[Mozilla黑客][24]的文章。方法是包装一个对象以防止扩展或修改。虽然
object.freeze`现在提供了将对象渲染为只读的功能,但是可以对这种方法进行扩展,以便访问不存在属性的枚举对象能更好地处理抛出错误。 const NOPE = () => { throw new Error("Can't modify read-only view"); }; const NOPE_HANDLER = { set: NOPE, defineProperty: NOPE, deleteProperty: NOPE, preventExtensions: NOPE, setPrototypeOf: NOPE }; const readOnlyView = target => new Proxy(target, NOPE_HANDLER);
const createEnum = target => readOnlyView( new Proxy(target, { get: (obj, prop) => { if (prop in obj) { return Reflect.get(obj, prop); } throw new ReferenceError(`Unknown prop "${prop}"`); } }) );
Object
,如果尝试访问不存在的属性现在不是返回 undefined
,而是会抛出异常。这使得在早期捕获和解决问题变得更加容易。 enum
示例也是代理上的代理的第一个示例,它确认代理是另一个代理的有效目标对象。这通过组合代理功能促进了代码重用。 let SHIRT_SIZES = createEnum({ S: 10, M: 15, L: 20 }); SHIRT_SIZES.S; // 10 SHIRT_SIZES.S = 15; // Uncaught Error: Can't modify read-only view SHIRT_SIZES.XL; // Uncaught ReferenceError: Unknown prop "XL"
nameOf
,它返回给定 enum
值的属性名,模仿Javascript等语言中的行为。 enum
类型,但是这个解决方案的独特之处在于,它使用普通Javascript,而不使用特殊的构建工具或转置器。 Proxy
用例是重载操作符的能力,比如使用handler.has的in操作符。 in
操作符用于检查指定的属性是否位于指定的对象或其原型链中。但它也是语法上最优雅的重载操作符。这个例子定义了一个连续 range
函数来比较数字。 const range = (min, max) => new Proxy(Object.create(null), { has: (_, prop) => +prop >= min && +prop <= max });
const X = 10.5; const nums = [1, 5, X, 50, 100]; if (X in range(1, 100)) { // true // ... } nums.filter(n => n in range(1, 10)); // [1, 5]
in
运算符,我们还可以重载 delete
和 new
。 cookie
进行交互,那么必须处理document.cookie。这是一个不寻常的API,因为API是一个 String
,它读出所有 cookie
,以 分号分隔。 document.cookie
是一个看起来像这样的字符串: _octo=GH1.2.2591.47507; _ga=GA1.1.62208.4087; has_recent_activity=1
document.cookie
比较麻烦且容易出错。一种方法是使用简单的cookie框架,可以适用于使用 Proxy。 const getCookieObject = () => { const cookies = document.cookie .split(";") .reduce( (cks, ck) => ({ [ck.substr(0, ck.indexOf("=")).trim()]: ck.substr( ck.indexOf("=") + 1 ), ...cks }), {} ); const setCookie = (name, val) => (document.cookie = `${name}=${val}`); const deleteCookie = name => (document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT;`); return new Proxy(cookies, { set: (obj, prop, val) => ( setCookie(prop, val), Reflect.set(obj, prop, val) ), deleteProperty: (obj, prop) => ( deleteCookie(prop), Reflect.deleteProperty(obj, prop) ) }); };
document.cookie
进行持久性的所有更改。 let docCookies = getCookieObject(); docCookies.has_recent_activity; // "1" docCookies.has_recent_activity = "2"; // "2" delete docCookies2["has_recent_activity"]; // true
cookie
提供了更好的交互,尽管在生产环境中还需要诸如字符串规范化之类的附加功能。 get
、 set
、 apply
和 construct trap
,并适用于IE9+。 确定一个对象是否是代理是不可能的
Proxy
目标和闭包来巧妙地重新分配对象的 Proxy
操作。 new Proxy("To be, or not to be...", {}); // TypeError: Cannot create proxy with a non-object as target or handler
Proxy
的最令人信服的理由是,上面的许多示例只有几行,并且可以轻松组合以创建复杂的功能。最后一个例子,我们可以从几个用例中组合函数来创建一个只读 cookie
对象,该对象返回不存在或“私有”隐藏cookie的默认值。 // document.cookie = "_octo=GH1.2.2591.47507; _ga=GA1.1.62208.4087; has_recent_activity=1" let docCookies = withZeroValue( hide(readOnlyView(getCookieObject())), "Cookie not found" ); docCookies.has_recent_activity; // "1" docCookies.nonExistentCookie; // "Cookie not found" docCookies._ga; // "Cookie not found" docCookies.newCookie = "1"; // Uncaught Error: Can't modify read-only view
转载地址:http://uyfpi.baihongyu.com/