前端学习笔记-Proxy和Reflext

498 阅读2分钟

捕获器的preventExtensions

捕获器的preventExtensions()可以检测到freeze、seal、preventExtensions这三个操作,因为他们都使对象无法再扩展(添加新属性):

const a = {
    name: 'xiaoming',
    age: 21,
};
const pro = new Proxy(a, {
    preventExtensions(target) {
        console.log('preventExtensions()');
        return Reflect.preventExtensions(...arguments)
    }
});
Object.freeze(pro)//preventExtensions()
Object.seal(pro)//preventExtensions()
Object.preventExtensions()//preventExtensions()

捕获器的get

捕获器get3个参数 (target, property, receiver),捕获器set4个参数 (target, property, value, receiver)

  • target —— 是目标对象,该对象作为第一个参数传递给 new Proxy,
  • property —— 目标属性名,
  • property —— 目标属性的值,
  • receiver —— 如果目标属性是一个 getter 访问器属性,则 receiver 就是本次读取属性所在的 this 对象。通常,这就是 proxy 对象本身(或者,如果我们从代理继承,则是从该代理继承的对象)。在捕获器的get和set函数里打印receiver或者console.log(arguments);会陷入死循环

Reflect需要注意的地方

const target = {
    _name: 'xiaoming',
    get name() {
            return this._name;
    },
};
const handler = {
    _name: 'xiaohong',
    get (target, property, receiver) {
        //console.log(arguments);
        //如果执行这个,因为会打印receiver,所以会陷入死循环
        //因为console.log(arguments)中有对receiver的引用,这个操作会触发get,陷入死循环,是一种内存泄露的情况
        return target[property];//**
        // return Reflect.get(target, property, receiver);
    },
};

const proxy = new Proxy(target, handler);
const admin = {
    __proto__: proxy,
    _name: 'xiaohuang',
}
console.log(admin.name);//xiaoming
console.log(proxy.name);//xiaoming

上面admin.name的值并不符合预期,如果把**标注的地方改为return Reflect.get(...arguments);则能打印出预期的值admin.name;//xiaohuang。因为Reflect.get会用第三个参数指定需要访问的this的指向。

比如:

console.log(Reflect.get(
    {
        _age: 1,
        get age() {
            return this._age;
        }
    },
    'age',
    {
        _age: 2
    },
));//2

这个例子会打印出2。

前边的例子中如果写为

const admin = {
    __proto__: proxy,
    _name: 'xiaohuang',
    name: 'Admin',
}

那么admin的name属性会被覆盖为'Admin'; 但如果像下边的写法,则会出问题:

const admin = {
    __proto__: proxy,
    _name: 'xiaohuang',
}
admin.name = 'Admin';

或者

const admin = Object.create(proxy);
admin.name = 'Admin';

这时如果打印admin.name的值,出来的还是admin._name的值。原因在于target里只定义了name的获取set,并没有定义相应的set,被认为是无法修改的。而执行admin.name = 'Admin'会进行set的操作。如果给target设置了对应的set name则会有预期的结果。

内置对象的内部插槽

Proxy在处理Map, Set, Date, Promise等对象的方法时,会遇到一些问题,相关操作不能被拦截到,因为他们使用了“内部插槽”的方式 如Map的set,或者Date的getTime方法。和this的指向相关。

解决的思路是,在捕获器内部将这个方法从目标源上调用。因为每次调用方法时,会先进入get拦截。

get(target, prop, receiver) {
    let value = Reflect.get(...arguments);
    return typeof value == 'function' ? value.bind(target) : value;
}

需要注意的地方

Proxy作为构造函数,但没有.prototype属性,Proxy.prototype === undefined; 同样作为构造函数但没有.prototype属性的,如通过bind返回的函数。箭头函数、Symbol和console.log这样的函数也没有.prototype,他们不能作为构造函数。

对象相等性测试===不能被代理拦截