js--代理与反射之捕获器和常见的反射 API

前言

  假如你已经初步了解了代理和反射的基本知识,知道了只要在代理对象上调用其处理程序,所有捕获器都会拦截它们对应的反射 API 操作。本文来总结一下代理对象的处理程序中不同的捕获器和反射API。

正文

  代理可以捕获 13 种不同的基本操作。这些操作有各自不同的反射 API 方法、参数、关联 ECMAScript操作和不变式(限制)。

  1、get () 捕获器--监听劫持对象属性的访问

  get() 捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get() 。

  (1)拦截的操作:(proxy为代理对象),get () 返回值无限制。

    a、proxy.property

    b、proxy[property]

    c、Object.create(proxy)[property]

    d、Reflect.get(proxy, property, receiver)

  (2)捕获器参数:

    a、target :目标对象。

    b、property :引用的目标对象上的字符串键属性。

    c、receiver :代理对象或继承代理对象的对象。

  (3)捕获器限制:

    a、如果目标对象的属性(target.property)存在不可配置且不可写的数据属性,get捕获器返回的值必须与目标对象的属性(target.property)匹配,否则报错TypeError。

    b、如果目标对象的属性(target.property)存在不可配置且[[Get]]为undefined的访问器属性,则 get 捕获器的返回值必须为 undefined。

  (4)get()捕获器的使用

  这里通过get捕获器进行对象外形的验证,对象外形( Object Shape )指的是对象已有的属性与方法的集合, JS 引擎使用对象外形来进行代码优化,经常会创建一些类来表示对象。如果你能大胆假设某个对象总是拥有与起始时相同的属性与方法(可以通过 Object.preventExtensions() 方法、 Object.seal() 方法或Object.freeze() 方法来达到这种效果),那么在访问不存在的属性时抛出错误在这种场合就会非常有用。代理能够让对象外形验证变得轻而易举。

  get捕获器会在读取对象属性时被调用,即使改属性在对象中不存在。代码如下:

        var person = {}
        var handler = {
            get(trapTarget, property, receiver) {
                if (!(property in receiver)) {
                    throw new TypeError("Property " + property + " doesn't exist.");
                }
                return Reflect.get(trapTarget, property, receiver);
            }
        };
        var perProxy = new Proxy(person, handler);
        perProxy.name = "proxy";// 添加属性的功能正常
        console.log(perProxy.name); // "proxy"
        // 读取不存在属性会抛出错误
        console.log(perProxy.age); // 抛出错误Property age doesn't exist.

  2、set () 捕获器--监听劫持对象属性值的设置

  set() 捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set() 。

  (1)拦截的操作:(proxy为代理对象)返回true表示设置成功,返回 false 表示失败,严格模式下会抛出 TypeError

    a、proxy.property = value

    b、proxy[property] = value

    c、Object.create(proxy)[property] = value

    d、Reflect.set(proxy, property, value, receiver)

  (2)捕获器参数:

    a、target :目标对象。

    b、property :引用的目标对象上的字符串键属性。

    c、value :要赋给属性的值。

    d、receiver :接收最初赋值的对象。

  (3)捕获器限制:

    a、如果目标对象的属性(target.property)存在不可配置且不可写的数据属性,get捕获器返回的值必须与目标对象的属性(target.property)匹配,否则报错TypeError。

    b、如果目标对象的属性(target.property)存在不可配置且[[Get]]为undefined的访问器属性,则 get 捕获器的返回值必须为 undefined。

  (4)set()捕获器的使用

  这里通过 set 捕获器验证新增属性值。假设你想要创建一个对象,并要求其属性值只能是数值,这就意味着该对象的每个新增属性都要验证,并且在属性值部为数值累心更多时候抛出错误。为此你需要定义set陷阱函数来重写设置属性值时的默认行为。代码如下:

        var person = {
            name: "Serendipity"
        };
        var handler = {
            set(trapTarget, property, value, receiver) {
                // 忽略已有属性,避免影响它们
                if (!trapTarget.hasOwnProperty(property)) {
                    if (isNaN(value)) {
                        throw new TypeError("Property must be a number.");
                    }
                }
                // 添加属性
                return Reflect.set(...arguments);
            }
        }
        let perProxy = new Proxy(person, handler);
        // 添加一个新属性
        perProxy.age = 18;
        console.log(perProxy.age); // 18
        console.log(person.age); // 18
        // 你可以为 name 赋一个非数值类型的值,因为该属性已经存在
        perProxy.name = "SerendipityProxy";
        console.log(perProxy.name); // "SerendipityProxy"
        console.log(person.name); // "SerendipityProxy"
        // 抛出错误-anotherName为新增属性,会判断值类型
        perProxy.anotherName = "Nicholas";// Uncaught TypeError: Property must be a number.

  3、has () 捕获器--监听拦截对象被 in 操作符调用

  has() 捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()。

  (1)拦截的操作:(proxy为代理对象)。has() 必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。

    a、property in proxy

    b、property in Object.create(proxy)

    c、with(proxy) {(property);}

    d、Reflect.has(proxy, property)

  (2)捕获器参数:

    a、target :目标对象。

    b、property :引用的目标对象上的字符串键属性。

  (3)捕获器限制:

    a、如果目标对象的属性 (target.property) 存在且不可配置,则处理程序必须返回 true 。

    b、如果目标对象的属性 (target.property) 存在且目标对象不可扩展,则处理程序必须返回 true 。

  (4)has()捕获器的使用

    a、in 运算符

   in 运算符用于判断指定对象中是否存在某个属性,如果对象的属性名与指定的字符串或符号值相匹配,那么 in 运算符应当返回 true ,无论该属性是对象自身的属性还是其原型的属性。例如:

                var target = {
                    value: 42
                }
                console.log("value" in target); // true
                console.log("toString" in target); // true

     b、隐藏对象中属性用过in操作符访问

        var person = {
            name: "Serendipity",
            age: 18
        };
        var handler = {
            has(trapTarget, property) {
                if (property === "value") {
                    return false;
                } else {
                    return Reflect.has(trapTarget, property);
                }
            }
        }
        var perProxy = new Proxy(person, handler);
        console.log("value" in perProxy); // false
        console.log("name" in perProxy); // true
        console.log("toString" in perProxy); // true

   4、deleteProperty() 捕获器--监听拦截对象被 delet 操作符调用

  deleteProperty() 捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect.deleteProperty() 。

  (1)拦截的操作:(proxy为代理对象)。deleteProperty() 必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。

    a、delete proxy.property

    b、delete proxy[property]

    c、Reflect.deleteProperty(proxy, property)

  (2)捕获器参数:

    a、target :目标对象。

    b、property :引用的目标对象上的字符串键属性。

  (3)捕获器限制:

  如果自有的 target.property 存在且不可配置,则处理程序不能删除这个属性。

  (4)deleteProperty()捕获器的使用

    a、delete 运算符

  delete 运算符能够从指定对象上删除一个属性,在删除成功时返回 true ,否则返回false 。如果试图用 delete 运算符去删除一个不可配置的属性,在严格模式下将会抛出错误;而非严格模式下只是单纯返回 false 。例如:

                var target = {
                    value: 42
                }
                console.log(delete target.value); // true
                console.log( target.value); // undefined
                console.log('value' in target) // false
                Object.defineProperty(target, "name", { 
                    configurable: false 
                });
                console.log(delete target.name); // false
                console.log('name' in target)// true

    b、deleteProperty()捕获器阻止对对象特定属性的删除

        var person = {
            name: "Serendipity",
            age: 18
        };
        var handler = {
            deleteProperty(trapTarget, property) {
                if (property === "name") {
                    return false;
                } else {
                    return Reflect.deleteProperty(trapTarget, property);
                }
            }
        }
        var perProxy = new Proxy(person, handler);
        console.log(delete perProxy.age);// true
        console.log(delete perProxy.name);// false

   5、getOwnPropertyDescriptor() 捕获器--监听拦截通过 Object.getOwnPropertyDescriptor()获取属性描述符时调用

        getOwnPropertyDescriptor() 捕获器会在 Object.getOwnPropertyDescriptor() 中被调用。对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor() 。

            (1)拦截的操作:(proxy为代理对象)。getOwnPropertyDescriptor() 必须返回对象,或者在属性不存在时返回 undefined 。

                            a、Object.getOwnPropertyDescriptor(proxy, property)

                            b、Reflect.getOwnPropertyDescriptor(proxy, property)

            (2)捕获器参数:

                            a、target :目标对象。

                            b、property :引用的目标对象上的字符串键属性。

            (3)捕获器限制:

                        a、如果自有的 target.property 存在且不可配置,则处理程序必须返回一个表示该属性存在的对象。

                        b、如果自有的 target.property 存在且可配置,则处理程序必须返回表示该属性可配置的对象。

                        c、如果自有的 target.property 存在且 target 不可扩展,则处理程序必须返回一个表示该属性存在的对象。

                        d、如果 target.property 不存在且 target 不可扩展,则处理程序必须返回 undefined 表示该属性不存在。

                        e、如果 target.property 不存在,则处理程序不能返回表示该属性可配置的对象。

            (4)getOwnPropertyDescriptor()捕获器的使用

                a、Object.getOwnPropertyDescriptor()

                Object.getOwnPropertyDescriptor()获取传入对象给定属性的描述符。

                var person = { name: "Serendipity", isAdult: true };
                Object.defineProperty(person, 'age', {
                    get: function () {
                        return this.age
                    },
                    set: function (newValue) {
                        this.isAdult = newValue >= 18 ? true : false
                    }
                });
                var descriptor = Object.getOwnPropertyDescriptor(person, 'age')
                console.log(descriptor);
                //configurable: false
                // enumerable: false
                // get: ƒ ()
                // set: ƒ (newValue)
                b、getOwnPropertyDescriptor()捕获器
        var person = {
            name: "Serendipity",
            age: 18
        };
        var handler = {
            getOwnPropertyDescriptor(trapTarget, property) {
                console.log('getOwnPropertyDescriptor()');
                return Reflect.getOwnPropertyDescriptor(...arguments)
            }
        }
        var perProxy = new Proxy(person, handler);
        Object.getOwnPropertyDescriptor(perProxy, 'name');
        // getOwnPropertyDescriptor()
        // configurable: true
        // enumerable: true
        // value: "Serendipity"
        // writable: true

  6、defineProperty() 捕获器--监听拦截通过 Object.defineProperty() 获取属性描述符时调用

        defineProperty() 捕获器会在 Object.defineProperty() 中被调用。对应的反射 API 方法为Reflect.defineProperty() 。

            (1)拦截的操作:(proxy为代理对象)。defineProperty() 必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。

                            a、Object.defineProperty(proxy, property, descriptor)

                            b、Reflect.defineProperty(proxy, property, descriptor)

            (2)捕获器参数:

                            a、target :目标对象。

                            b、property :引用的目标对象上的字符串键属性。

                            c、descriptor :包含可选的 enumerable 、 configurable 、 writable 、 value 、 get 和 set定义的对象。

            (3)捕获器限制:

                        a、如果目标对象不可扩展,则无法定义属性。

                        b、如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。

                        c、如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。

            (4)defineProperty()捕获器的使用

                a、Object.defineProperty()

                Object.defineProperty()修改对象内部属性。

                Object.defineProperty()修改对象内部属性。
                    var person = {};
                    Object.defineProperty(person, "age", {
                        enumerable: true,
                        configurable: false,
                        value: 18
                    });
                    console.log(person.age);// 18
                    delete person.age
                    console.log(person.age);// 18
                b、defineProperty()捕获器阻止对象某些属性修改内部特性
        var person = {
            name: "Serendipity",
            age: 18
        };
        var handler = {
            defineProperty(trapTarget, property, descriptor) {
                if (property == "isAdult") { return false }
                return Reflect.defineProperty(...arguments)
            }
        }
        var perProxy = new Proxy(person, handler);
        console.log(perProxy.name);
        Object.defineProperty(perProxy, "isAdult", {
            enumerable: true,
            configurable: false,
            value: false
        });// 报错:Uncaught TypeError: 'defineProperty' on proxy: trap returned falsish for property 'isAdult'

  7、setPrototypeOf() 捕获器--监听拦截通过 Object.setPrototypeOf() 设置对象原型时调用

  setPrototypeOf() 捕获器会在 Object.setPrototypeOf() 中被调用。对应的反射 API 方法为Reflect.setPrototypeOf() 。

            (1)拦截的操作:(proxy为代理对象)。setPrototypeOf() 必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。

                            a、Object.setPrototypeOf(proxy)

                            b、Reflect.setPrototypeOf(proxy)

            (2)捕获器参数:

                            a、target :目标对象。

                            b、property :引用的目标对象上的字符串键属性。

            (3)捕获器限制:

                        如果 target 不可扩展,则唯一有效的 prototype 参数就是 Object.getPrototypeOf(target)的返回值。

  8、getPrototypeOf() 捕获器--监听拦截通过 Object.getPrototypeOf() 获取对象原型时调用

  getPrototypeOf() 捕获器会在 Object.getPrototypeOf() 中被调用。对应的反射 API 方法为Reflect.getPrototypeOf()

            (1)拦截的操作:(proxy为代理对象)。getPrototypeOf() 必须返回对象或 null 。

                            a、Object.getPrototypeOf(proxy)

                            b、Reflect.getPrototypeOf(proxy)

                            c、proxy.__proto__

                            d、Object.prototype.isPrototypeOf(proxy)

                            e、proxy instanceof Object

            (2)捕获器参数:

                            a、target :目标对象。

            (3)捕获器限制:

                        如果 target 不可扩展,则 Object.getPrototypeOf(proxy) 唯一有效的返回值就是 Object.getPrototypeOf(target) 的返回值

  9、isExtensible() 捕获器--监听拦截通过 Object.isExtensible() 判断一个对象是否是可扩展的时调用

  isExtensible() 捕获器会在 Object.isExtensible() 中被调用。对应的反射 API 方法为Reflect.isExtensible() 。

            (1)拦截的操作:(proxy为代理对象)。isExtensible() 必须返回布尔值,表示 target 是否可扩展。返回非布尔值会被转型为布尔值。

                            a、Object.isExtensible(proxy)

                            b、Reflect.isExtensible(proxy)

            (2)捕获器参数:

                            a、target :目标对象。

                            b、property :引用的目标对象上的字符串键属性。

            (3)捕获器限制:

                            a、如果 target 可扩展,则处理程序必须返回 true 。

                            b、如果 target 不可扩展,则处理程序必须返回 false 。

            (4)Object.isExtensible的使用

                var empty = {};
                Object.isExtensible(empty);

  10、preventExtensions() 捕获器--监听拦截通过 Object.preventExtensions() 让一个对象变的不可扩展时调用

  preventExtensions() 捕获器会在 Object.preventExtensions() 中被调用。对应的反射 API方法为 Reflect.preventExtensions() 。

            (1)拦截的操作:(proxy为代理对象)。preventExtensions() 必须返回布尔值,表示 target 是否已经不可扩展。返回非布尔值会被转型为布尔值。

                            a、Object.preventExtensions(proxy)

                            b、Reflect.preventExtensions(proxy)

            (2)捕获器参数:

                            a、target :目标对象。

            (3)捕获器限制:

                        如果 Object.isExtensible(proxy) 是 false ,则处理程序必须返回 true 。

            (4)Object.preventExtensions()的使用  

            var empty = {};
            Object.isExtensible(empty) //=== true
            Object.preventExtensions(empty);
            Object.isExtensible(empty) //=== false

  11、apply() 捕获器--监听拦截通过函数被调用时调用

  apply() 捕获器会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply() 。

            (1)拦截的操作:(proxy为代理对象)。返回值无限制。

                            a、proxy(...argumentsList)

                            b、Function.prototype.apply(thisArg, argumentsList)

                            c、Function.prototype.call(thisArg, ...argumentsList)

                            d、Reflect.apply(target, thisArgument, argumentsList)

            (2)捕获器参数:

                            a、target :目标对象。

                            b、thisArg :调用函数时的 this 参数。

                            c、argumentsList :调用函数时的参数列表

            (3)捕获器限制:

                        目标对象target 必须是一个函数对象。

            (4)apply()捕获器的使用

    a、apply()

            const myTarget = () => {};
            const proxy = new Proxy(myTarget, {
            apply(target, thisArg, ...argumentsList) {
            console.log('apply()');
            return Reflect.apply(...arguments)
            }
            });
            proxy();
            // apply()

    b、apply()捕获器进行函数参数验证

        function foo(...nums) {
            return nums.sort();
        }
        var handler = {
            apply(target, thisArg, argumentsList) {
                for (const arg of argumentsList) {
                    if (typeof arg !== 'number') {
                        throw 'argement must be number';
                    }
                }
                return Reflect.apply(...arguments);
            }
        }
        const fooProxy = new Proxy(foo, handler);
        console.log(fooProxy(5, 1, 3)); // [1,3,5]
        console.log(fooProxy(5, 1, '3'));// Error: argement must be number

  12、construct() 捕获器--监听拦截对象被 New 操作符调用时调用

  construct() 捕获器会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct() 。

            (1)拦截的操作:(proxy为代理对象)。construct() 必须返回一个对象。

                            a、new proxy(...argumentsList)

                            b、Reflect.construct(target, argumentsList, newTarget)

            (2)捕获器参数:

                            a、target :目标构造函数。

                            b、argumentsList :传给目标构造函数的参数列表。

                            c、newTarget :最初被调用的构造函数。

            (3)捕获器限制:

                        目标对象target 必须可以用作构造函数。

            (4)construct()捕获器的使用

            const myTarget = function() {};
            const proxy = new Proxy(myTarget, {
            construct(target, argumentsList, newTarget) {
                console.log('construct()');
                return Reflect.construct(...arguments)
            }
            });
            new proxy;
            // construct()

  b、construct() 验证构造函数参数

        function Person(name) {
            this.name = name
        }
        var handler = {
            construct(target, argumentsList, newTarget) {
                if (argumentsList[0] === undefined) {
                    throw "Person cannot be instantiated without name"
                } else {
                    return Reflect.construct(...arguments)
                }
            }
        }
        var porPerson = new Proxy(Person, handler)
        var Serendipity = new porPerson("Serendipity")
        var s = new porPerson()//Person cannot be instantiated without name

  13、ownKeys() 捕获器--监听拦截Object.keys() 及类似方法中被调用

  ownKeys() 捕获器会在 Object.keys() 及类似方法中被调用。对应的反射 API 方法为 Reflect.ownKeys() 。
            (1)拦截的操作:(proxy为代理对象)。ownKeys() 必须返回包含字符串或符号的可枚举对象。
                            a、Object.getOwnPropertyNames(proxy)
                            b、Object.getOwnPropertySymbols(proxy)
                            c、Object.keys(proxy)
                            d、Reflect.ownKeys(proxy)
            (2)捕获器参数:
                            target :目标对象。
            (3)捕获器限制:
                        a、返回的可枚举对象必须包含 target 的所有不可配置的自有属性。
                        b、如果 target 不可扩展,则返回可枚举对象必须准确地包含自有属性键。
            (4)ownKeys()捕获器的使用
            const myTarget = {};
            const proxy = new Proxy(myTarget, {
                ownKeys(target) {
                    console.log('ownKeys()');
                    return Reflect.ownKeys(...arguments)
                }
            });
            Object.keys(proxy);
            // ownKeys()

写在最后

  以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。

 

posted @ 2022-01-05 16:42  zaisy'Blog  阅读(288)  评论(0编辑  收藏  举报