美文网首页
一次性搞懂 ES6 的 Proxy 与 Reflect

一次性搞懂 ES6 的 Proxy 与 Reflect

作者: 前端好有趣 | 来源:发表于2021-01-26 15:35 被阅读0次

    Proxy

    Proxy 意为 代理,通俗来说,就是在 目标数据对象 外围设置一层 拦截

    什么是拦截?

    举个栗子:老板有一个仓库(对象),刚开始大家可以任意在其中存放货物(获取、修改、存放值)。后来老板觉得自己的仓库每次被使用得太随意了,就请了一个管家(拦截对象),所有人存取货物都必须经过管家之手。老板说:我制定一个拦截方案,那就是每次存取货物,管家都要大声把物品的信息念出来(console.log 一下)。

    据上所得三个关键词:

    • 仓库
    • 管家
    • 拦截方案

    get(target, key, receiver)

    下面用一段简单的代码来表述下:

    // 原对象
    let obj = {
        name:'steve',
        age:20
    }
    
    // data 是我们要拦截的 原对象
    // dataProxy 是我们新生成的 拦截对象
    let dataProxy = new Proxy(obj, {
      get(target, key, receiver) {
        // target 就是我们的原对象,obj
        console.log("target >>> ", target === obj);
        // key 就是操作的健
        console.log("key >>> ", key);
        // receiver 就是 dataProxy,拦截对象
        console.log("receiver >>> ", receiver);
        console.log(`当前值为:${target[key]}`);
        return target[key];
      },
    });
    
    // 使用拦截器
    dataProxy.name;
    
    // 打印结果:
    //  -> 当前值为:steve
    //  -> "steve"
    

    然后,相应的关系就出来了:

    • 仓库 --- obj
    • 管家 --- dataProxy
    • 拦截方案 --- set(),get()
    使用 proxy 实现 节点创建

    在上面,我们已经学会了如何使用 proxy 实现对对象的拦截,接下来,我们用一个例子来说明 proxy 在实际项目中可以用来干些什么。

    // 首先创建我们的 proxy 代理对象
    const DOMR = new Proxy(
      {},
      {
        get(target, key) {
          // 每次访问 DOMR 的属性,都返回一个函数,该函数可以创建节点
          // attr 要创建的元素的属性对象
          // children 要创建的元素的内容
          return function (attr = {}, ...children) {
            const el = document.createElement(key);
            // 遍历 属性对象,为元素添加属性
            for (let key of Object.keys(attr)) {
              el.setAttribute(key, attr[key]);
            }
            for (let child of children) {
              // 遍历 内容,为元素添加 内容
              if (typeof child === "string") {
                child = document.createTextNode(child);
              }
              el.appendChild(child);
            }
            // 返回创建的元素
            return el;
          };
        },
      }
    );
    
    // 只要我们调用 DOMER.xxx 就创建一个 xxx 节点
    let div = DOMR.div(
      { id: "div1", class: "aaa" },
      // 只要我们调用 DOMER.xxx 就创建一个 xxx 节点
      DOMR.a({ href: "https://www.baidu.com", class: "hello" }, "这是一个连接")
    );
    
    // 打印出节点
    console.log(div);
    
    // window.onload,必须等到页面内包括图片的所有元素加载完毕后才能执行。
    window.onload = function () {
      // 添加节点到页面上
      document.body.appendChild(div);
    };
    

    set(target, key, value, receiver)

    用来拦截对象的设置过程

    let person = new Proxy(
      {},
      {
        // target 原对象 {}
        // key 操作的键
        // value 被设置的值
        // receiver 就是 person,拦截对象
        set(target, key, value, receiver) {
          if (key === "age") {
            if (!Number.isInteger(value)) {
              throw new TypeError("年龄必须为整数!");
            }
            if (value > 100) {
              throw new RangeError("年龄不能大于100!");
            }
          }
          target[key] = value;
        },
      }
    );
    person.age = 20;
    

    apply(target, context, args)

    拦截 Proxy 实例作为函数调用的操作,比如 proxy(...args)proxy.call(object, ...args)proxy.apply(...)

    let obj = {name:'obj'}
    function say() {
      console.log("hello world");
    }
    let newFn = new Proxy(say, {
      // target 目标函数
      // context 目标函数的上下文 this
      // args 函数参数
      apply(target, object, args) {
        return 'hello'
      },
    });
    
    // target:say
    // context:obj
    // args:[1,2,3]
    newFn.call(obj,1,2,3)
    // 打印结果:hello
    // 并没有打印出 hello world,说明,原函数内部代码并不会执行,如需执行我们需要在 apply 中调用 say()
    

    其它方法

    • has(target, key):拦截key in proxy的操作,返回一个布尔值。
    • deleteProperty(target, key):拦截delete proxy[key]的操作,返回一个布尔值。
    • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
    • getOwnPropertyDescriptor(target, key):拦截Object.getOwnPropertyDescriptor(proxy, key),返回属性的描述对象。
    • defineProperty(target, key, propDesc):拦截Object.defineProperty(proxy, key, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
    • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
    • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
    • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
    • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
    • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

    proxy 与 Object.defineproperty 的区别

    Proxy 提供了13种拦截方法,包括拦截 constructorapplydeleteProperty 等等,而 Object.defineProperty 只有 getset

    Reflect

    reflect:反射,一般用来将语言内部的一些方法,直接暴露到这个对象上,供人们使用。比如 Function 的 apply,Object 的 设置抽成 set 等等。

    Reflect.apply(func, context, args)

    效果跟 Function 的 apply 很相似,可以用任意对象作为上下文调用函数

    function show(...args) {
      console.log(this);
      console.log(args);
    }
    
    Reflect.apply(show, null, [1, 2, 3, 4]);
    // 打印结果:
    // window
    // [1,2,3,4]
    

    Reflect.set(target, name, value, receiver)

    设置target对象的name属性等于value

    var myObject = {
      foo: 1,
    };
    Reflect.set(myObject, "foo", 2);
    myObject.foo; // 2
    

    Proxy 与 Reflect 结合使用案例

    实现数据的双向绑定

    let vm = {
      list: [1, 2, 3, 4]
    }
    
    let vmProxy = new Proxy(vm.list, {
      set (target, prop, value) {
        console.log(`Setting: ${value}`);
        Reflect.set(target, prop, value);
        return true;
      }
    })
    
    vmProxy[0] = 3
    
    // 打印结果:[3, 2, 3, 4]
    console.log(vm.list)
    

    如果有同学想要系统学习,请移步 ECMAScript 6 - Proxy

    参考文献

    https://segmentfault.com/a/1190000015483195
    https://www.jianshu.com/p/77eaaf34e732
    https://juejin.cn/post/6844904088119853063
    https://juejin.cn/post/6844904090116292616#heading-6
    https://es6.ruanyifeng.com/#docs/proxy
    https://my.oschina.net/u/4333555/blog/4329035
    https://blog.csdn.net/XuM222222/article/details/98846376
    https://blog.csdn.net/weixin_43574780/article/details/108042951

    相关文章

      网友评论

          本文标题:一次性搞懂 ES6 的 Proxy 与 Reflect

          本文链接:https://www.haomeiwen.com/subject/vqigzktx.html