灏天阁

代理

· Yin灏

Reflect 对象

Reflect 对象时 ES6 引入的对象,通常与代理一起使用,通过代理可修改对象的默认行为,而 Reflect 对象则提供了一些与代理的处理器一一对应的方法,用于处理对象的默认行为。

Reflect 对象是内置对象,且没有构造函数,不能使用 new 离开创建实例,

Reflect 对象主要包括 13 个静态方法

方法名 说明
apply 对一个函数进行调用操作。
defineProperty 为对象定义或修改一个属性,与 Object.defineProperty 的主要不同在于返回值,Object.defineProperty 返回的是调用它时的第一个参数,而 Reflect.defineProperty 返回的是布尔值,操作成功时返回 true,失败时返回 false。
deleteProperty 删除对象的一个属性,操作类似 delete 操作符。在操作成功时返回 true,失败时返回 false。
get 返回对象的属性值。
getOwnPropertyDescriptor 返回指定的对象属性的属性描述符,如果属性不存在,返回 undefined。与 Object.getOwnPropertyDescriptor 的区别在于如何处理非对象目标。
getPrototypeOf 返回对象的原型,与 Object.getPrototypeOf 的主要区别在于 Object.getPrototypeOf 会进行类型转换,再获取原型,而 Reflect.getPrototypeOf 不会。
has 用于判断属性是否包含在对象内,与 in 操作符作用相同。
isExtensible 用于判断一个对象是否可扩展,与 Object.isExtensible 的主要区别在于,当接收的参数不是一个对象时, Object.isExtensible 会返回 false,而 Reflect.isExtensible 则会抛出一个错误。
ownKeys 返回由对象自身可枚举属性组成的数组。
preventExtensions 将对象设置为不可扩展对象,与 Object.preventExtensions 的主要区别在于,无论传递给Object.preventExtensions 的参数是什么类型的值,都会返回参数值,而 Reflect.preventExtensions 在参数不是对象时会抛出错误。
set 为对象的属性设置值。
setPrototypeOf 为对象指定原型,与 Object.setPrototypeOf 的主要区别在于返回值, Object.setPrototypeOf 在失败时会抛出错误。而 Reflect.setPrototypeOf 在成功时返回 true,在失败时返回 false。

使用代理

let target = {};

console.log(target.y); // undefined
// 处理目标对象的对象,即代理对象
let handler = {
    get: function(target, key) {
        return key in target ? target[key] : 1;
    }
}

let proxy = new Proxy(target, handler);

console.log(proxy.x); // 1

proxy.x = 2;

console.log(proxy.x); // 2
console.log(proxy.y); // 1

可代理的操作

  1. getprototypeOf 操作

操作 getPrototypeOf 用于返回对象的原型,在执行以下 5 种操作时会触发该操作:

  • Object.getprototypeOf
  • Reflect.getprototypeOf
  • __proto__
  • Object.prototype.isPrototypeOf()
  • intanceof
  1. setPrototypeOf 操作

操作 setPrototypeOf 可用来为对象设置一个原型,在执行 Object.setPrototypeOfReflect.setPrototypeOf 时会触发该操作。

如果不希望对象在使用时被修改原型对象,可以通过修改 setPrototypeOf 的默认行为,返回 false 或 抛出错误就能轻松实现。

let target = {};

console.log(target.y); // undefined

let handler = {
    setPrototypeOf: function(target, newProto) {
        return false;
        // throw new Error('不能修改原型)
    }
}

let proxy = new Proxy(target, handler);

Object.setPrototypeOf(proxy, Array); // 会抛出类型错误
  1. isExtensible 操作

判断对象是否可以扩展,在调用 Object.isExtensibleReflect.isExtensible 时会触发该操作。

修改这个操作的默认行为一定要小心,修改行为后的返回值必须与原对象在调用 Object.isExtensible 时返回的值相同,不然会报错。

let target = {};

let handler = {
    isExtensible: function(target) {
        return false;
    }
}

let proxy = new Proxy(target, handler);
// 位置1
console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // 抛出错误类型

以上代码中, Object.isExtensible(target) 返回的时 true,因为 Object.isExtentsible(proxy) 也必须返回 true,如果类似代码中那样返回 false,就会抛出类型错误。

如果在 位置1 ,添加 Object.preventExtensions(target); 将 target 修改为不可扩展的对象,代码就不会抛出类型错误,这时,如果将返回值修改为 true,又会抛出类型错误。

  1. preventExtensions 操作

将一个对象转变为一个不可扩展的对象,也就是让对象不能添加新的成员。在调用 Object.preventExtensions(target)Reflect.preventExtensible 时会触发。

  1. getOwnPrototypeDescriptor 操作

用于返回对象自有属性的特性,在调用 Object.getOwnPrototypeDescriptorReflect.getOwnPrototypeDescriptor 时会触发该操作。

修改该操作的行为需要注意的是,如果返回结果不符合下列条件,就会抛出类型错误:

  • 返回值不是对象或 undefined
  • 如果属性是目标对象的不可配置属性(configurable 为 false),那么返回的属性特性与属性自身的特性不相符,例如以下代码:
let target = {};

Object.defineProperty(target, "x", {
    enumerable: false,
    configurable: false,
    writable: false,
    value: 1
})

let handler = {
    getOwnPropertyDescriptor: function(target, property) {
        return {
            value: 1,
            writable: false,
            enumerable: false,
            configurable: false
        }
    }
}

let proxy = new Proxy(target, handler);

console.log(Object.getOwnPropertyDescriptor(proxy, 'x')); // {value: 1, writable: false, enumerable: false, configurable: false}

以上代码为 target 定义了一个不可配置的属性 x,要在自定义的 getOwnPropertyDescriptor 中返回 x 的特性,就必须与 x 的自身特性相符,不然会报错。

const obj = {
    x: 1
}
console.log(Object.getOwnPropertyDescriptor(obj, 'x')); // {value: 1, writable: true, enumerable: true, configurable: true}
console.log(Reflect.getOwnPropertyDescriptor(obj, 'x'));
  1. defineProperty 操作

用于定于属性的特征,在调用 Object.definePropertyReflect.defineProperty 时会触发该操作。

  1. has 操作

用于判断对象是否还有某个属性,在执行以下操作的时候会触发:

  • 属性查询:property in proxy
  • 继承属性查询:property in Object.create(proxy)
  • with 检查:with(proxy) {(property )}
  • Relect.has()

通过重写 has 操作,可以帮助对象隐藏某些属性

let target = { x: 1 };

let handler = {
    has: function(target, property) {
        if (property === 'x') return false;
        return true;
    }
}

let proxy = new Proxy(target, handler);

console.log('x' in proxy); // false
  1. get 操作

返回属性的值,在执行以下操作时会触发

  • 访问属性:proxy[‘property’] 和 proxy.property
  • 访问原型链上的属性:Object.create(proxy)[property]
  • Reflect.get()

在自定义该操作时,需要注意的是,当要访问的目标属性是不可写且不可配置的时候,返回值必须与属性值相同,不然会报错。

let target = {};

let handler = {
    get: function(target, property, receiver) {
        console.log(receiver); // proxy
        if (!(property in receiver)) {
            throw new TypeError("属性 " + property + " 不存在.")
        }
        return Reflect.get(target, property, receiver)
    }
}

let proxy = new Proxy(target, handler);

console.log(proxy['x']); // false
  1. set 操作

用于设置属性值,在执行以下操作时触发

  • 设置属性值:proxy[‘property’] = value 和 proxy.property = value
  • 设置继承者的属性值:Object.create(proxy)[property] = value
  • Reflect.set()

通过自定义 set 操作,可以实现赋值验证

let target = { x: 1 };

let handler = {
    set: function(target, property, value) {
        if (property === 'x' && isNaN(value)) {
            throw new TypeError("属性 " + property + " 的值非法.")
        }
        return Reflect.set(target, property, value);
    }
}

let proxy = new Proxy(target, handler);

proxy['x'] = 2;

console.log(proxy['x']); // 2

proxy['x'] = 'xxx'; // 报错
  1. deleteProperty 操作

用于删除属性,在执行以下操作时触发

  • 删除属性:delete proxy[property] 和 delete proxy.property
  • Reflect.deleteProperty()

通过自定义操作,可以避免属性被删除

let target = { x: 1, y: 2 };

let handler = {
    deleteProperty: function(target, property) {
        if (property === 'x') {
            throw new TypeError("属性 " + property + " 不能删除")
        }
        return Reflect.deleteProperty(target, property)
    }
}

let proxy = new Proxy(target, handler);

delete proxy['y'];

console.log(proxy); // Proxy {x: 1}

delete proxy['x'];
  1. ownKeys 操作

用于返回由对象自身可枚举属性组成的数组,在执行以下操作时触发

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • Reflect.ownKeys()

在自定义的时候注意,返回的必须是数组。

通过自定义操作,可以把私有属性过滤掉

let target = { _x: 1, y: 2 };

let handler = {
    ownKeys: function(target) {
        return Reflect.ownKeys(target).filter(key => {
            return typeof key !== 'string' || key[0] !== '_'
        })
    }
}

let proxy = new Proxy(target, handler);

console.log(Object.getOwnPropertyNames(target)); // ['_x', 'y']
console.log(Object.getOwnPropertyNames(proxy)); // ['y']
  1. apply 操作

用于调用函数,在执行以下操作时触发

  • proxy(…args)
  • Function.prototype.apply()Function.prototype.call()
  • Reflect.apply()

在自定义的时候注意,目标对象必须是函数对象

let target = function(x, y, z) {
    return true;
}

let handler = {
    apply: function(target, thisArg, argumentsList) {
        console.log(argumentsList); // [1, 2, 3]
        return argumentsList[0] + argumentsList[1] + argumentsList[2];
    }
}

let proxy = new Proxy(target, handler);

console.log(proxy(1, 2, 3)); // 6
  1. construct 操作

主要用于拦截 new 操作符,在使用 new 操作符创建实例或调用 Reflect.construct 方法时会触发该操作。

自定义的 construct 操作必须返回一个对象,不然会抛出一个类型错误。

可撤销的代理对象

通过代理的 revocable 方法创建一个可撤销的代理对象

let target = { x: 1 };

let proxy = Proxy.revocable(target, {});

console.log(proxy); // {proxy: Proxy, revoke: ƒ}

console.log(proxy.proxy.x); // 1

proxy.revoke()

console.log(proxy.proxy.x); // 抛出类型错误

调用 revocable 返回的是一个对象,在对象内包含代理对象,以及一个 revoke 方法,用于撤销代理对象。

- Book Lists -