ES6之Proxy

来源:互联网 发布:卷积神经网络算法原理 编辑:程序博客网 时间:2024/05/21 08:40

Proxy

  ES6 中新增的 Proxy 特性为开发者提供了一种实现元编程的“大杀器”。
  元编程概念可以简单地描述为:一个程序可以对另外一个程序进行读取、转换,甚至在这第二个程序运行的时候对其进行修改

  Proxy 让我们可以对任何对象的绝大部分行为进行监听和干涉,实现更多的自定义程序行为


  目录:

  • Proxy
    • 使用语法
    • has
    • get
    • set
    • apply
    • construct
    • 创建可解除 Proxy 对象
    • 使用场景
      • 看似不可能的自动填充
      • 只读试图
      • 入侵式测试框架


使用语法

  用法:new Proxy(target, handler)
  与 Getter/Setter 不同的是,Proxy 并不是以语法的形式使用,Proxy 通过设置行为监听方法捕获程序对对应对象的行为。

const obj = {};const proxy = new Proxy(obj, {    // ...})

  Proxy 的构造器接受两个参数,第一个参数为需要进行包装的目标对象,第二个参数则为用于监听目标对象行为的监听器,其中监听器可以接受一些参数以监听相对应的程序行为。

监听属性、参数及监听内容

属性值 监听器参数 监听内容 has (target, prop) 监听 in 语句的使用 get (target, prop, reciver) 监听目标对象的属性读取 set (target, prop, value, reciver) 监听目标对象的属性赋值 deleteProperty (target, prop) 监听 delete 语句对目标对象的删除属性行为 ownKeys (target) 监听 Object.getOwnPropertyName() 的读取 apply (target, thisArg, arguments) 监听目标函数(作为目标对象)的调用行为 construct (target, arguments, newTarget) 监听目标构造函数(作为目标对象)利用 new 而生成实例的行为 getPrototypeOf (target) 监听 Objext.getPrototypeOf() 的读取 setPrototypeOf (target, prototype) 监听 Objext.setPrototypeOf() 的调用 isExtensible (target) 监听 Objext.isExtensible() 的读取 preventExtensions (target) 监听 Objext.preventExtensions() 的读取 getOwnPropertyDescriptor (target, prop) 监听 Objext.getOwnPropertyDescriptor() 的调用 defineProperty (target, property, descriptor) 监听 Object.defineProperty() 的调用

has

  可以通过为 Proxy 的 handler 定义 has 监听方法,来监听程序通过 in 语句来检查一个字符串或数字是否为该 Proxy 的目标对象中某个属性的属性键的过程。

const p = new Proxy({}, {    has(target, prop){        console.log(`Checking "${prop}" is in the target or not`);        return true;    }})console.log('foo' in p);// Checking "foo" is in the target or not// true

  该监听方法有两个需要注意的地方,如果遇到这两种情况,便会抛出 TypError 错误
  1.当目标对象被其他程序通过 Object.preventExtensions() 禁用了属性拓展(该对象无法再增加新的属性,只能对当前已有的属性进行操作,包括读取、操作和删除,但是一旦删除就无法再定义)功能,且被检查的属性键确实存在与目标对象中,该监听方法便不能返回 false

const obj = {foo: 1};Object.preventExtensions(obj);const p = new Proxy(obj, {    has(target, prop){        console.log(`Checking "${prop}" is in the target or not`);        return false;    }})console.log('foo' in p);    // TypeError: 'has' on proxy: trap returned falsish for property 'foo' but the proxy target is not extensible

  2.当被检查的属性键存在与目标对象中,且该属性的 configurable 配置是 false 时,该监听方法不能返回 false

const obj = {};Object.defineProperty(obj, 'foo', {    configurable: false,    value: 10})const p = new Proxy(obj, {    has(target, prop){        console.log(`Checking "${prop}" is in the target or not`);        return false;    }})console.log('foo' in p);// TypeError: 'has' on proxy: trap returned falsish for property 'foo' which exists in the proxy target as non-configurable

get

  Getter只能对已知的属性键进行监听,而无法对所有属性读取行为进行拦截,而 Proxy 可以通过设定 get 监听方法,拦截和干涉目标对象的所有属性读取行为。

const obj = {foo: 1};const p = new Proxy(obj, {    get(target, prop){        console.log(`Program is trying to fetch the property "${prop}".`);        return target[prop];    }})p.foo;  // Program is trying to fetch the property "foo".p.something;    // Program is trying to fetch the property "something".

  这个监听方法也存在需要注意的地方——当目标对象被读取属性的 configurablewritable 属性都为 false 时,监听方法最后返回的值必须与目标对象的原属性值一致

const obj = {};Object.defineProperty(obj, 'foo', {    configurable: false,    enumberable: false,    value: 10,    writable: false})const p = new Proxy(obj, {    get(target, prop){        return 20;    }})console.log(p.foo);// TypeError: 'get' on proxy: property 'foo' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '10' but got '20')

set

  handler.set 用于监听目标对象的所有属性赋值行为

const obj = {};const p = new Proxy(obj, {    set(target, prop, value){        console.log(`Setting value "${value}" on the key "${prop}" in the target object`);        target[prop] = value;        return true;    }})p.foo = 1;  // Setting value "1" on the key "foo" in the target object

注意,如果目标对象自身的某个属性是不可写也不可配置的,那么 set 不得改变这个属性的值,只能返回同样的值,否则报错。


apply

  handler.apply , Proxy 也为作为目标对象的函数提供了监听其调用行为的属性。

const sum = function(...args){    return args            .map(Number)            .filter(Boolean)            .reduce((a, b) => a + b);}const p = new Proxy(sum, {    apply(target, thisArg, args){        console.log(`Function is being called with arguments [${args.join()}] and context ${thisArg}`);        return target.call(thisArg, ...args);    }})console.log(p(1, 2, 3));// Function is being called with arguments [1,2,3] and context undefined// 6

construct

  handler.construct, Proxy 也可以将作为目标监听对象,并监听其通过 new 语句来生产新实例的行为,这同样可以使用再作为构造器的构造函数上。

class Foo{};const p = new Proxy(Foo, {    construct(target, args, newTarget){        return {arguments: args}    // 这里返回的结果会是 new 所得到的实例}})const obj = new p(1, 2, 3);console.log(obj.arguments);  // [1, 2, 3]

创建可解除 Proxy 对象

  用法:Proxy.revocable(target, handler) : (proxy, revoke)

const obj = {foo: 10};const revocable = Proxy.revocable(obj, {    get(target, prop){        return 20;    }})const proxy = revocable.proxy;console.log(proxy.foo); // 20revocable.revoke();console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked

  Proxy.revocable(target, handler) 会返回一个带有两个属性的对象,其中一个 proxy 便是该函数所生成的可解除 Proxy 对象,而另一个 revoke 则是将刚才的 Proxy 对象解除的解除方法


使用场景

看似“不可能”的自动填充

class Tree{    constructor(){        return new Proxy({}, {            get(target, prop){                if(!(prop in target)) target[prop] = new Tree();                return target[prop];            }        })    }}const tree = new Tree();tree.brance1.brance2.leaf = 1;tree.brance1.brance2.brance3.leaf = 2;


只读试图

  通过 Proxy 来对渲染数据对象的行为进行监听和干涉,对所有可能对渲染数据做出修改的行为进行拦截。

const NOPE = () => {    throw new TypeError('Cannot modify the readonly data.');}function readonly(data){    return new Proxy(data, {        set: NOPE,        deleteProperty: NOPE,        setPrototypeOf: NOPE,        preventExtensions: NOPE,        defineProperty: NOPE    })}const data = {foo: 10};const readonlyData = readonly(data);readonlyData.foo = 2;   // TypeError: Cannot modify the readonly data.

  上面的方法并没有对深层结果进行修改拦截,即 readonlyData.foo.bar = 2; 是不会被拦截的,而且对表层数据的 get 行为也没有拦截。
  我们可以对其进行修改,使其实现真正的“只读视图”:

function readonly(data){    return new Proxy(data, {        get(target, prop){            const result = target[prop];            // 判断是否为引用类型            if(Object(result) === result){                return readonly(result);            }            return result;        },        // ...    })}const data = {foo: {bar: 1}};const readonlyData = readonly(data);readonlyData.foo.bar = 2;   //TypeError: Cannot modify the readonly data.


入侵式测试框架

  开发者可以通过使用 Proxy 在定义方法和逻辑代码之间建立一个隔离层,这样便可以在两个地方都无须做大量修改的情况下,实现一下非常深入的测试,从而开发出一个针对业务的入侵式测试框架
  我们通过 Proxy 对目标代码进行包装,比如我们对一些目标 API 方法(函数)打一个“钩子”(Hook),通过一些包装,可以对一些方法进行计时,此处使用 console.time 来进行简单的计时。

import api from './api';export default hook(api, {    methodName(fn){        return funciton(..args){            console.time('methodName');            return fn(...arga)                    .then((...args) => {                        console.timeEnd('methodName');                        return Promise.resolve(...args);                    })        }    }})

  为了实现这样的“钩子”机制,我们可以对目标对象的 get 行为进行监听和干涉,一旦发现属性键所对应的属性值为一个函数(方法),且在测试案例中存在相应的干涉机制,便将其传递到测试案例中。

function hook(obj, cases){    return new Proxy(obj, {        get(target, prop){            if((prop in cases) && (typeof target[prop] === 'function')){                const fn = target[prop];                return cases[prop](fn);            }            return target[prop];        }    })}
原创粉丝点击