A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 绮罗 中级黑马   /  2020-2-20 20:31  /  2232 人查看  /  8 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 绮罗 于 2020-2-20 20:44 编辑

Proxy 介绍

使用Proxy,你可以将一只猫伪装成一只老虎。下面大约有6个例子,我希望它们能让你相信,Proxy 提供了强大的 Javascript 元编程。
尽管它不像其他ES6功能用的普遍,但Proxy有许多用途,包括运算符重载,对象模拟,简洁而灵活的API创建,对象变化事件,甚至Vue 3背后的内部响应系统提供动力。
Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。
ES6原生提供了Proxy构造函数,用来生成Proxy实例。
var proxy = new Proxy(target, handler);
Proxy对象的所有用法,都是上面的这种形式。不同的只是handle参数的写法。其中new Proxy用来生成Proxy实例,target是表示所要拦截的对象,handle是用来定制拦截行为的对象。
下面是 Proxy 最简单的例子是,这是一个有陷阱的代理,一个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
结果是一个对象将为任何属性访问操作都返回“42”。这包括target.x,target['x'],Reflect.get(target, 'x')等。
但是,Proxy 陷阱当然不限于属性的读取。它只是十几个不同陷阱中的一个:
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
Proxy 用例


默认值/“零值”
在 Go 语言中,有零值的概念,零值是特定于类型的隐式默认结构值。其思想是提供类型安全的默认基元值,或者用gopher的话说,给结构一个有用的零值。
虽然不同的创建模式支持类似的功能,但Javascript无法用隐式初始值包装对象。Javascript中未设置属性的默认值是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
此功能可能有用的一个地方是坐标系。绘图库可以基于数据的形状自动支持2D和3D渲染。不是创建两个单独的模型,而是始终将z默认为 0 而不是undefined,这可能是有意义的。
负索引数组
在JS中获取数组中的最后一个元素方式通过写的很冗长且重复,也容易出错。这就是为什么有一个TC39提案定义了一个便利属性Array.lastItem来获取和设置最后一个元素。
其他语言,如Python和Ruby,使用负组索引更容易访问最后面的元素。例如,可以简单地使用arr[-1]替代arr[arr.length-1]访问最后一个元素。
使用 Proxy 也可以在 Javascript 中使用负索引。
const negativeArray = els =>
    new Proxy(els, {
        get: (target, propKey, receiver) =>
            Reflect.get(
                target,
                +propKey < 0 ? String(target.length + +propKey) : propKey,
                receiver
            )
    });
一个重要的注意事项是包含handler.get的陷阱字符串化所有属性。对于数组访问,我们需要将属性名称强制转换为Numbers,这样就可以使用一元加运算符简洁地完成。
现在[-1]访问最后一个元素,[-2]访问倒数第二个元素,以此类推。
Proxy 介绍使用Proxy,你可以将一只猫伪装成一只老虎。下面大约有6个例子,我希望它们能让你相信,Proxy 提供了强大的 Javascript 元编程。
尽管它不像其他ES6功能用的普遍,但Proxy有许多用途,包括运算符重载,对象模拟,简洁而灵活的API创建,对象变化事件,甚至Vue 3背后的内部响应系统提供动力。
Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。
ES6原生提供了Proxy构造函数,用来生成Proxy实例。
var proxy = new Proxy(target, handler);
Proxy对象的所有用法,都是上面的这种形式。不同的只是handle参数的写法。其中new Proxy用来生成Proxy实例,target是表示所要拦截的对象,handle是用来定制拦截行为的对象。
下面是 Proxy 最简单的例子是,这是一个有陷阱的代理,一个get陷阱,总是返回42。
let target = {    x: 10,    y: 20};let hanler = {    get: (obj, prop) => 42};target = new Proxy(target, hanler);target.x; //42target.y; //42target.x; // 42
结果是一个对象将为任何属性访问操作都返回“42”。 这包括target.x,target['x'],Reflect.get(target, 'x')等。
但是,Proxy 陷阱当然不限于属性的读取。 它只是十几个不同陷阱中的一个:
  • 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
Proxy 用例




默认值/“零值”在 Go 语言中,有零值的概念,零值是特定于类型的隐式默认结构值。其思想是提供类型安全的默认基元值,或者用gopher的话说,给结构一个有用的零值。
虽然不同的创建模式支持类似的功能,但Javascript无法用隐式初始值包装对象。Javascript中未设置属性的默认值是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, undefinedpos = withZeroValue(pos, 0);console.log(pos.z, pos.y, pos.z); // 4, 19, 0
此功能可能有用的一个地方是坐标系。 绘图库可以基于数据的形状自动支持2D和3D渲染。 不是创建两个单独的模型,而是始终将z默认为 0 而不是undefined,这可能是有意义的。
负索引数组在JS中获取数组中的最后一个元素方式通过写的很冗长且重复,也容易出错。 这就是为什么有一个TC39提案定义了一个便利属性Array.lastItem来获取和设置最后一个元素。
其他语言,如Python和Ruby,使用负组索引更容易访问最后面的元素。例如,可以简单地使用arr[-1]替代arr[arr.length-1]访问最后一个元素。
使用 Proxy 也可以在 Javascript 中使用负索引。
const negativeArray = els =>    new Proxy(els, {        get: (target, propKey, receiver) =>            Reflect.get(                target,                +propKey < 0 ? String(target.length + +propKey) : propKey,                receiver            )    });
一个重要的注意事项是包含handler.get的陷阱字符串化所有属性。 对于数组访问,我们需要将属性名称强制转换为Numbers,这样就可以使用一元加运算符简洁地完成。
现在[-1]访问最后一个元素,[-2]访问倒数第二个元素,以此类推。

隐藏属性众所周知 JS 没有私有属性。 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之类的陷阱。 除了闭包之外,这可能是最接近真正私有属性的方法,因为它们无法通过枚举,克隆,访问或修改来访问。
[图片上传失败...(image-ea9ab5-1564194070930)]
但是,它们在开发控制台中可见。 只有闭包才能免于这种命运。
缓存在客户端和服务器之间同步状态时遇到困难并不罕见。数据可能会随着时间的推移而发生变化,很难确切地知道何时重新同步的逻辑。
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))    });};
这个函数过于简化了:它使对象上的所有属性在一段时间后都无法访问。然而,将此方法扩展为根据每个属性设置生存时间(TTL),并在一定的持续时间或访问次数之后更新它并不困难。
let bankAccount = ephemeral(    {        balance: 14.93    },    10);console.log(bankAccount.balance); // 14.93setTimeout(() => {    console.log(bankAccount.balance); // undefined}, 10 * 1000);
这个示例简单地使银行帐户余额在10秒后无法访问。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
枚举和只读视图这些例子来自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; // 10SHIRT_SIZES.S = 15;// Uncaught Error: Can't modify read-only viewSHIRT_SIZES.XL;// Uncaught ReferenceError: Unknown prop "XL"
这种方法可以进一步扩展,包括模拟方法nameOf,它返回给定enum值的属性名,模仿Javascript等语言中的行为。
虽然其他框架和语言超集(比如TypeScript)提供enum类型,但是这个解决方案的独特之处在于,它使用普通Javascript,而不使用特殊的构建工具或转置器。
运算符重载也许从语法上讲,最吸引人的 Proxy 用例是重载操作符的能力,比如使用handler.has的in操作符。
in操作符用于检查指定的属性是否位于指定的对象或其原型链中。但它也是语法上最优雅的重载操作符。这个例子定义了一个连续range函数来比较数字。
const range = (min, max) =>    new Proxy(Object.create(null), {        has: (_, prop) => +prop >= min && +prop <= max    });
与Python不同,Python使用生成器与有限的整数序列进行比较,这种方法支持十进制比较,可以扩展为支持其他数值范围。
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对象如果你曾经与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
在11行代码中,修改cookie提供了更好的交互,尽管在生产环境中还需要诸如字符串规范化之类的附加功能。
细节决定成败,Proxy 也不例外。
Polyfill在撰写本文时(2019年5月),Proxy 没有完整的 polyfill。然而,有一个由谷歌编写的 partial polyfill for Proxy ,它支持get、set、apply和construct trap,并适用于IE9+。
它是 Proxy 吗?
确定一个对象是否是代理是不可能的
根据Javascript语言规范,无法确定对象是否是代理。 但是,在 Node 10+上,可以使用util.types.isProxy方法。
目标是什么?给定一个代理对象,就不可能获得或更改目标对象。也不可能获取或修改处理程序对象。
最近似的是Ben Nadel的文章Using Proxy to Dynamically Change THIS Binding,它使用一个空对象作为Proxy目标和闭包来巧妙地重新分配对象的Proxy操作。
Proxy 原语new Proxy("To be, or not to be...", {});// TypeError: Cannot create proxy with a non-object as target or handler
不幸的是,Proxy的一个限制是目标必须是Object。 这意味着我们不能直接使用像String这样的原语。

8 个回复

倒序浏览
好厉害哦,你们真棒
回复 使用道具 举报
很好
回复 使用道具 举报
够够的棒
回复 使用道具 举报
加油同学,加油黑马!
回复 使用道具 举报
努力!奋斗!
回复 使用道具 举报
加油加油
回复 使用道具 举报
回复 使用道具 举报
加油
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马