你不知道的JavaScript(十二):元编程 - 代理、Reflect API

元编程 - 代理

let obj = {
    
    
    name: 'Lee',
    age: 18
};

let proxyObj = new Proxy(obj, {
    
    
    /**
     * target       对象的引用-obj
     * p            对象内的属性名-key
     * receiver     self/接收者/代理
     */
    get(target, p, receiver) {
    
    
        console.log('【get】---> ', target, p, receiver);
        // Reflect.get 转发,
        return Reflect.get(target, p, receiver);
    },

    /**
     * 在设定属性值(不管是新增的还是更新已有的)时由默认 set(..) 处理函数的步骤触发的
     */
    getOwnPropertyDescriptor(target, p) {
    
    
        console.log('【getOwnPropertyDescriptor】---> ', target, p);
        return Object.getOwnPropertyDescriptor(target, p);
    },

    /**
     * 在设定属性值(不管是新增的还是更新已有的)时由默认 set(..) 处理函数的步骤触发的
     */
    defineProperty(target, property, attributes) {
    
    
        console.log('【defineProperty】---> ', target, property, attributes);
        return Object.defineProperty(target, property, attributes);
    }
});

proxyObj;
proxyObj.name;
proxyObj.name = 'Tom';
目标对象 / 函数代理上可以定义的处理函数 如何 / 何时被触发
get(..) 通过 [[Get]],在代理上访问一个属性(Reflect.get(..)、. 属性运算符或 [ .. ] 属性运算符)
set(..) 通过 [[Set]],在代理上设置一个属性值(Reflect.set(..)、赋值运算符 = 或目标为对象属性的解构赋值)
deleteProperty(..) 通过 [[Delete]], 从代理对象上删除一个属性(Reflect.deleteProperty(..)delete
apply(..)(如果目标为函数) 通过 [[Call]],将代理作为普通函数 / 方 法 调 用(Reflect.apply(..)call(..)apply(..)(..) 调用运算符)
construct(..)(如果目标为构造函数) 通过 [[Construct]],将代理作为构造函数调用(Reflect.construct(..)new
getOwnPropertyDescriptor(..) 通过 [[GetOwnProperty]],从代理中提取一个属性描述符(Object.getOwnPropertyDescriptor(..)Reflect.getOwnPropertyDescriptor(..)
defineProperty(..) 通过 [[DefineOwnProperty]],在代理上设置一个属性描述符(Object.defineProperty(..)Reflect.defineProperty(..)
getPrototypeOf(..) 通过 [[GetPrototypeOf]],得到代理的 [[Prototype]]Object.getPrototypeOf(..)Reflect.getPrototypeOf(..)__proto__Object#isPrototypeOf(..)instanceof
setPrototypeOf(..) 通过 [[SetPrototypeOf]],设置代理的 [[Prototype]]Object.setPrototypeOf(..)Reflect.setPrototypeOf(..)__proto__
preventExtensions(..) 通过 [[PreventExtensions]],使得代理变成不可扩展的(Object.prevent Extensions(..)Reflect.preventExtensions(..)
isExtensible(..) 通过 [[IsExtensible]],检测代理是否可扩展(Object.isExtensible(..)Reflect.isExtensible(..)
ownKeys(..) 通过 [[OwnPropertyKeys]],提取代理自己的属性和 / 或符号属性(Object.keys(..)Object.getOwnPropertyNames(..)Object.getOwnSymbolProperties(..)Reflect.ownKeys(..)JSON.stringify(..)
enumerate(..) 通过 [[Enumerate]],取得代理拥有的和“继承来的”可枚举属性的迭代器(Reflect.enumerate(..)for..in
has(..) 通过 [[HasProperty]],检查代理是否拥有或者“继承了”某个属性(Reflect.has(..)Object#hasOwnProperty(..)"prop" in obj
  • 代理局限性(对原对象的一些操作)
    typeof obj; 
    String(obj);
    obj + ""; 
    obj == proxyObj; 
    obj === proxyObj;
    
  • 可取消代理
    let obj = {
          
          
        name: 'Lee',
        age: 18
    };
    
    let handler = {
          
          
        /**
         * target       对象的引用-obj
            * p            对象内的属性名-key
            * receiver     self/接收者/代理
            */
        get(target, p, receiver) {
          
          
            console.log('【get】---> ', target, p, receiver);
            // Reflect.get 转发,
            return Reflect.get(target, p, receiver);
        },
    
        /**
         * 在设定属性值(不管是新增的还是更新已有的)时由默认 set(..) 处理函数的步骤触发的
            */
        getOwnPropertyDescriptor(target, p) {
          
          
            console.log('【getOwnPropertyDescriptor】---> ', target, p);
            return Object.getOwnPropertyDescriptor(target, p);
        },
    
        /**
         * 在设定属性值(不管是新增的还是更新已有的)时由默认 set(..) 处理函数的步骤触发的
            */
        defineProperty(target, property, attributes) {
          
          
            console.log('【defineProperty】---> ', target, property, attributes);
            return Object.defineProperty(target, property, attributes);
        }
    };
    
    let {
          
           proxy, revoke } = Proxy.revocable(obj, handler);
    
    proxy;
    proxy.name;
    proxy.name = 'Tom';
    
    // 可取消代理
    revoke();
    proxy;
    proxy.name; // Error
    proxy.name = 'Tom'; // Error
    

使用代理

通常可以把代理看作是对目标对象的“包装”。在这种意义上,代理成为了代码交互的主要对象,而实际目标对象保持隐藏 / 被保护的状态

代理在先

  • 代理与目标交流
let arr = [
    'prosper,lee!',
    ['你好,世界!', 123, 'ABC'], // 不会处理到
];

let proxyArr = new Proxy(arr, {
    
    
    /**
     * target       对象的引用-obj
     * p            对象内的属性名-key
     * receiver     self/接收者/代理
     */
    get(target, p, receiver) {
    
    

        // 判断是否为字符串值
        if (typeof target[p] === 'string') {
    
    
            // 过滤掉标点符号
            return target[p].replace(/[^\w]/g, '');
        }

        // 所有其他的传递下去
        return target[p];
    },

    set(target, p, newValue, receiver) {
    
    

        // 判断新赋值的属性值是否为字符串
        if (typeof newValue == "string") {
    
    
            newValue = newValue.toLowerCase();

            // 如果不存在新的赋值,那么push
            if (target.indexOf(newValue) === -1) {
    
    
                target.push(newValue);
            }
        }
        return true;
    }
});

proxyArr.push('HELLO...', 42, 'Prosper,Lee!', 'World!!');

arr.forEach(d => {
    
    
    // prosper,lee!
    // (3) ['你好,世界!', 123, 'ABC']
    // hello...
    // world!!
    console.log(d); 
});

proxyArr.forEach(d => {
    
    
    // prosperlee
    // (3) ['你好,世界!', 123, 'ABC']
    // hello
    // world
    console.log(d); 
});

在这里插入图片描述
在这里插入图片描述

代理在后

  • 目标与代理交流
let proxy = new Proxy({
    
    }, {
    
    
    get(target, p, receiver) {
    
    
        return function () {
    
    
            console.log(target, p, receiver);
            receiver.logKey(p + "!");
        };
    }
});

let obj = {
    
    
    logKey(who = "someone") {
    
    
        console.log("hello", who);
    }
};

// 设定 obj 回退到 proxy
Object.setPrototypeOf(obj, proxy);

obj.logKey();            // hello someone 
obj.logKey("world");     // hello world
obj.everyone();          // hello everyone!

这里直接与 obj 而不是 prxoy 交流。当调用 logKey(..) 的时候,它在 obj上被找到并直接使用。

但是当我们试图访问像 everyone() 这样的方法的时候,这个函数在 obj 上并不存在。

No Such Property/Method

当试着访问或设置一个还不存在的属性时,默认情况下对象不是非常具有防御性。

所以你可能希望预先定义好一个对象的所有属性 / 方法之后,访问不存在的属性名时能够抛出一个错误。

  • 代理在前设计:
    let obj = {
          
          
        a: 1,
        foo() {
          
          
            console.log("a:", this.a);
        }
    };
    let proxyObj = new Proxy(obj, {
          
          
        get(target, p, receiver) {
          
          
            if (Reflect.has(target, p)) {
          
          
                return Reflect.get(target, p, receiver);
            } else {
          
          
                throw "No such property/method!";
            }
        },
        set(target, p, newValue, receiver) {
          
          
            if (Reflect.has(target, p)) {
          
          
                return Reflect.set(target, p, newValue, receiver);
            } else {
          
          
                throw "No such property/method!";
            }
        }
    });
    
    proxyObj.a = 3;
    proxyObj.foo(); // a: 3 
    
    proxyObj.b = 4; // Error: Uncaught No such property/method!
    proxyObj.bar(); // Error: Uncaught No such property/method!
    
  • 代理在后设计:
    let obj = {
          
          
        a: 1,
        foo() {
          
          
            console.log("a:", this.a);
        }
    };
    
    let proxy = new Proxy({
          
          }, {
          
          
        get() {
          
          
            throw "No such property/method!";
        },
        set() {
          
          
            throw "No such property/method!";
        }
    });
    
    // 设定 obj 回退到 proxy
    Object.setPrototypeOf(obj, proxy);
    
    obj.a = 3;
    obj.foo(); // a: 3 
    
    obj.b = 4; // Error: Uncaught No such property/method!
    obj.bar(); // Error: Uncaught No such property/method!
    

代理 hack [[Prototype]]

[[Prototype]] 机制运作的主要通道是 [[Get]] 运算。当直接对象中没有找到一个属性的时候,[[Get]] 会自动把这个运算转给 [[Prototype]] 对象处理。

使用代理的 get(..) 来模拟或扩展这个 [[Prototype]] 机制的概念,通过 [[Prototype]] 连成环状(或者,至少看起来是这样!)。实际上并不能创建一个真正的 [[Prototype]] 环,因为引擎会抛出错误。但是可以用代理模拟!

let obj = {
    
    
    name: "obj-1",
    foo() {
    
    
        console.log("foo:", this.name);
    }
};

let obj1 = new Proxy(obj, {
    
    
    get(target, p, receiver) {
    
    
        if (Reflect.has(target, p)) {
    
    
            return Reflect.get(target, p, receiver);
        } else {
    
     // 伪环状[[Prototype]] 
            return Reflect.get(target[Symbol.for("[[Prototype]]")], p, receiver);
        }
    }
});

let obj2 = Object.assign(Object.create(obj1), {
    
    
    name: "obj-2",
    bar() {
    
    
        console.log("bar:", this.name);
        this.foo();
    }
});

// 伪环状 [[Prototype]] 链接
obj1[Symbol.for("[[Prototype]]")] = obj2;

obj1.bar();
// bar: obj-1 <-- 通过代理伪装 [[Prototype]] 
// foo: obj-1 <-- this上下文依然保留着

obj2.foo(); // foo: obj-2 <-- 通过[[Prototype]]

元编程 - Reflect API

Reflect 对象是一个平凡对象(就像 Math),不像其他内置原生值一样是函数 / 构造器

  • 访问 / 查看一个对象键
API 描述
Reflect.ownKeys(..) 返回所有“拥有”的(不是“继承”的)键的列表,就像 Object.getOwnPropertyNames(..)Object.getOwnPropertySymbols(..) 返回的一样
Reflect.enumerate(..) 返回一个产生所有(拥有的和“继承的”)可枚举的(enumerable)非符号键集合的迭代器。本质上说,这个键的集合和 foo..in 循环处理的那个键的集合是一样的
Reflect.has(..) 实质上和 in 运算符一样,用于检查某个属性是否在某个对象上或者在它的 [[Prototype]] 链上。比如,Reflect.has(o, "foo") 实质上就是执行 foo" in o
  • 函数调用和构造器调用可以通过使用下面这些工具手动执行,与普通的语法(比如,(..)new)分开
API 描述
Reflect.apply(..) 举例来说,Reflect.apply(foo,thisObj,[42,"bar"])thisObj 作为 this 调用 foo(..) 函数,传入参数 42 和 “bar”
Reflect.construct(..) 举例来说,Reflect.construct(foo,[42,"bar"]) 实质上就是调用 new foo(42,"bar")
  • 可以使用下面这些工具来手动执行对象属性访问、设置和删除
API 描述
Reflect.get(..) 举例来说,Reflect.get(o,"foo") 提取 o.foo
Reflect.set(..) 举例来说,Reflect.set(o,"foo",42) 实质上就是执行 o.foo = 42
Reflect.deleteProperty(..) 举例来说,Reflect.deleteProperty(o,"foo") 实质上就是执行 delete o.foo

Reflect 的元编程能力提供了模拟各种语法特性的编程等价物,把之前隐藏的抽象操作暴露出来。比如,你可以利用这些能力扩展功能和 API,以实现领域特定语言(DSL)

属性排序

在 ES6 之前,一个对象键 / 属性的列出顺序是依赖于具体实现,并未在规范中定义。

一般来说,多数引擎按照创建的顺序进行枚举,虽然开发者们一直被强烈建议不要依赖于这个顺序

对于 ES6 来说,拥有属性的列出顺序是由 [[OwnPropertyKeys]] 算法定义的,这个算法产生所有拥有的属性(字符串或符号),不管是否可枚举。这个顺序只对 Reflect.ownKeys(..)(以及扩展的 Object.getOwnPropertyNames(..)Object.getOwnPropertySymbols(..))有保证

  • 其顺序为:
    1. 首先,按照数字上升排序,枚举所有整数索引拥有的属性;
    2. 然后,按照创建顺序枚举其余的拥有的字符串属性名;
    3. 最后,按照创建顺序枚举拥有的符号属性;
 var o = {
    
    
    [Symbol("c")]: 'yay',
    2: true,
    1: true,
    b: 'Hello',
    a: 'Lee'
};

Reflect.ownKeys(o);                 // (5) ['1', '2', 'b', 'a', Symbol(c)]
Object.getOwnPropertyNames(o);      // (4) ['1', '2', 'b', 'a']
Object.getOwnPropertySymbols(o);    // [Symbol(c)]
Object.keys(o);                     // (4) ['1', '2', 'b', 'a']

猜你喜欢

转载自blog.csdn.net/weixin_43526371/article/details/127132616