第十七节:Proxy代理对象和Reflect反射对象详解

一. Proxy详解

1. 抛砖引玉

   需求:有一个对象,我们希望监听这个对象中的属性被设置获取的过程。

   解决方案:可以使用 Object.defineProperty 的存储属性描述符(set、get)来对属性的操作进行监听。

   弊端:Object.defineProperty设计的初衷,不是为了去监听截止一个对象中的所有属性的。(它的初衷是为了定义普通属性,并定义属性的一些特点,可写、可遍历等)

   另外:如果我们想监听更加丰富的操作,比如新增、删除属性,Object.defineProperty是无能无力的。

{
    console.log("---------------------1. 抛砖引玉---------------------");
    const obj = {
        name: "ypf",
        age: 20,
    };
    // 遍历监听每个属性
    Object.keys(obj).forEach(key => {
        let myValue = obj[key];
        Object.defineProperty(obj, key, {
            get() {
                console.log(`监听到obj对象的${key}属性被访问了`);
                return myValue;
            },
            set(newValue) {
                console.log(`监听到obj对象的${key}属性被设置值`);
                myValue = newValue;
            },
        });
    });
    obj.name = "ypf";
    console.log(obj.age);
}

2. Proxy含义

    ES6中,新增了Proxy代理类,如果我们想监听一个对象的相关操作,就可以基于该对象创建一个Proxy代理对象,之后对该对象的所有操作(如:修改、获取等),都通过代理对象完成,并且代理对象可以监听到我们对原对象进行的那些操作。

 

3. Proxy的基本使用

(1). 如何创建代理对象?

      标准格式: const p = new Proxy(target,handler); target表示需要被代理的对象,hander中用来配置一下捕获器,用来监听对象

      比如:我要监听obj对象,则  const objProxy=new Proxy(obj,{});

      注意:后续对象的操作,都需要通过代理对象来进行,否则无法触发监听

(2). 最常用的get和set捕获器

      用来监听属性的读、写 操作

      A. get(target, key, receiver) 参数含义依次为:被监听(代理)的对象、被获取属性的key、调用的代理对象(即obj)

      B. set(target, key, newValue, receiver) 参数含义依次为:被监听(代理)的对象、被获取属性的key、调用的代理对象

代码分享:
{
    console.log("--------2. 使用proxy监听对象的获取和设置操作----------");
    const obj = {
        name: "ypf",
        age: 20,
    };
    const objProxy = new Proxy(obj, {
        // 获取时的捕获器
        get(target, key) {
            console.log(`监听到obj对象的${key}属性被访问了`);
            return target[key];
        },
        // 设置值时的捕获器
        set(target, key, newValue) {
            console.log(`监听到obj对象的${key}属性被设置值`);
            target[key] = newValue;
        },
    });
    // 下面设置和获取属性都需要通过代理对象进行 objProxy, 否则无法触发监听
    objProxy.name = "ypf1";
    console.log(objProxy.age);
}

4. Proxy其它捕获器

(1). has():  in 操作符的捕捉器

(2). deleteProperty(): delete 操作符的捕捉器

(3). apply():  函数调用操作的捕捉器

(4). construct(): new 操作符的捕捉器

共有13个捕获器:

代码分享1:

// 3.1 has和defineProperty捕获器
{
    console.log("---------3.1 has和defineProperty捕获器-----------");
    const obj = {
        name: "ypf",
        age: 20,
    };
    const objProxy = new Proxy(obj, {
        // 监听in的捕获器
        has(target, key) {
            console.log(`监听到obj对象的${key}属性的in操作`);
            return key in target;
        },
        // 监听delete的捕获器
        deleteProperty(target, key) {
            console.log(`监听到obj对象的${key}属性的delete操作`);
            delete target[key];
        },
    });
    // 测试
    console.log("name" in objProxy);
    delete objProxy.name;
}
View Code

代码分享2: 

// 3.2 apply和construct
{
    console.log("---------3.2 apply和construct-----------");
    function foo() {}
    const fooProxy = new Proxy(foo, {
        apply: function (target, thisArg, argArray) {
            console.log("对foo函数进行了apply调用");
            return target.apply(thisArg, argArray);
        },
        construct: function (target, argArray, newTarget) {
            console.log("对foo函数进行了new调用");
            return new target(...argArray);
        },
    });

    fooProxy.apply({}, ["abc", "cba"]);
    new fooProxy("abc", "cba");
}
View Code

 

5. 捕获器receiver参数的作用

    如下例子:改变了上面obj对象中this为ObjProxy,所以下面会调用两次 name一次,_name一次 (如果不传递receiver参数,只监听name,不监听_name)

代码分享:
// 4. 捕获器receiver参数的作用
{
    console.log("--------4. 捕获器receiver参数的作用----------");
    const obj = {
        _name: "ypf",
        get name() {
            return this._name;
        },
        set name(newValue) {
            this._name = newValue;
        },
    };
    const objProxy = new Proxy(obj, {
        get: function (target, key, receiver) {
            console.log(`监听到obj对象的${key}属性被访问了`);
            console.log(receiver === objProxy); //true  receiver是创建出来的代理对象
            //改变了上面obj对象中this为ObjProxy,所以下面会调用两次 name一次,_name一次 (如果不传递receiver参数,只监听name,不监听_name)
            return Reflect.get(target, key, receiver);
        },
        set: function (target, key, newValue, receiver) {
            console.log(`监听到obj对象的${key}属性被设置值`);
            Reflect.set(target, key, newValue, receiver);
        },
    });

    // 测试
    // objProxy.name = "lmr";
    console.log(objProxy.name);
}
View Code

 

6. 补充几个使用场景?

(1). 读操作的拦截--做非空判断

      之前需要每个属性使用 || 或 ??,现在只需在代理对象的get捕获器中统一判断就行

{
    console.log("-----5.1  读操作的拦截--做非空判断-----");
    const obj = {
        name: "ypf",
        age: 17,
    };
    //之前的模式
    console.log(obj.height || ""); //返回空字符串
    console.log(obj.height ?? ""); //返回空字符串

    // 现在可以使用代理,就不用每个属性都去单独判空了
    let objProxy = new Proxy(obj, {
        get(target, key) {
            return Reflect.has(target, key) ? target[key] : "";
        },
    });
    // 调用
    console.log(objProxy.height); //返回空字符串
}

(2). 写操作的拦截--服务端获取的数据希望是只读,不允许在任何一个环节被修改

      之前遍历key,使用Object.defineProperty,设置writable: false,

      现在只需要在代理的set捕获器中return false

{
    console.log(
        "5.2 写操作的拦截--服务端获取的数据希望是只读,不允许在任何一个环节被修改"
    );

    let jsonData = {
        status: "ok",
        msg: "获取成功",
        data: {},
    };
    // 之前的模式
    Object.keys(jsonData).forEach(key => {
        Object.defineProperty(jsonData, key, {
            writable: false,
        });
    });
    // 使用代理
    let jsonDataProxy = new Proxy(jsonData, {
        set(target, key, newValue) {
            return false;
        },
    });
}

(3). 格式校验

      之前的校验都写在业务中,现在可以使用proxy分离

{
    // Validator.js
    export default (obj, key, value) => {
        if (Reflect.has(key) && value > 20) {
            obj[key] = value;
        }
    };

    import Validator from "./Validator";
    let data = new Proxy(response.data, {
        set: Validator,
    });
}

(4). 实例一个对象,每个对象都有一个自己的 id 而且只读

{
    class Component {
        constructor() {
            this.proxy = new Proxy({
                id: Math.random().toString(36).slice(-8),
            });
        }
        get id() {
            return this.proxy.id;
        }
    }
}

 

二. Reflect详解

1. 说明

    Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。

(1).那么这个Reflect有什么用呢?

     A. 它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法

     B. 比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();

     C. 比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty() ;

(2). 如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?

     A. 这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;

     B. 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适

     C. 另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;

     D. 所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;

 (3).那么Object和Reflect对象之间的API关系,可以参考MDN文档:

      https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods

2. Reflect常用方法

    Reflect中有常见的方法和Proxy是一一对应的,也是13个。

   最常见的用法,和Proxy同时使用:  因为Proxy是代理对象,既然已经代理,就不要使用原先对象了,比如在 get() set() 方法内部,可以使用Reflect的方法进行操作。

代码分享:

{
    console.log("--------1. Reflect和Proxy同时使用---------");
    const obj = {
        name: "ypf",
        age: 20,
    };
    const objProxy = new Proxy(obj, {
        // 获取时的捕获器
        get(target, key) {
            console.log(`监听到obj对象的${key}属性被访问了`);
            return Reflect.get(target, key);
        },
        // 设置值时的捕获器
        set(target, key, newValue) {
            console.log(`监听到obj对象的${key}属性被设置值`);
            Reflect.set(target, key, newValue);
        },
    });
    // 下面设置和获取属性都需要通过代理对象进行 objProxy, 否则无法触发监听
    objProxy.name = "ypf1";
    console.log(objProxy.age);
}

3. Reflect.construct的作用

     Reflect.construct(target, argumentsList[, newTarget]),对构造函数进行 new 操作,相当于执行 new target(...args)。

     注意:如果传递newTarget,最后创建出来的对象是newTarget对象

 代码分享:

{
    console.log("---------------2. Reflect.construct的作用-------------------");
    function Student(name, age) {
        this.name = name;
        this.age = age;
    }

    function Teacher() {}

    // const stu = new Student("why", 18)
    // console.log(stu)
    // console.log(stu.__proto__ === Student.prototype)

    // 执行Student函数中的内容, 但是创建出来对象是Teacher对象
    const teacher = Reflect.construct(Student, ["ypf", 18], Teacher);
    console.log(teacher);
    console.log(teacher.__proto__ === Teacher.prototype);
}

 

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

 

posted @ 2022-03-27 21:01  Yaopengfei  阅读(561)  评论(1编辑  收藏  举报