ECMAScript 6 Proxy

Proxy主要用于定义基本操作的自定义行为,包括属性查找、赋值、枚举、函数调用等。

Proxy(target, handler)构造函数接收两个参数,target为被代理的对象,handler为处理器对象,用来自定义代理对象的各种操作。目前,一共有 13 种可代理操作:

get(target, propery, receiver)

在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。

const target = {
    name: 'aaa',
};
const targetProxy = new Proxy(target, {
    // 在读取代理对象的某个属性时触发该操作
    get(target, property, receiver) {
        return target[property] || null;
    }
});

targetProxy.name === target.name; // true
targetProxy.x === null; // true

通过proxyget拦截可以轻松实现 readOnly 和单例模式。

set(target, propKey, value, receiver)

在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1时。

const proxy = new Proxy({}, {
  set(obj, property, value) {
    if (property === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 150 || value < 0) {
        throw new RangeError('Wooo. Are you kinding me?');
      }
    } else if (property.startsWith('_')) {
      throw new Error('Can not write property which start with "_"')
    }
    // 对于满足条件的 age 属性以及其他属性,直接保存
    obj[prop] = value;
    return true;
  }
});

proxy.age; // undefined
proxy.age = 24;
proxy.age; // 24
proxy.age = '24' // TypeError: The age is not an integer
proxy.age = -1 // RangeError: Wooo. Are you kinding me?
proxy._sex = 'male'; // Error: Can not write property which start with "_"

可以通过设置代理对象的set处理器轻松的根据需求对对象的写入进行控制。

deleteProperty(target, property)

在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo时。

const proxy = new Proxy({
  _a: 1,
  a: 2,
}, {
  deleteProperty(target, property) {
    if (property.startsWith('_')) {
      throw Error('Can not delete property which start with "_"')
    }
    return delete target[property];
  }
});

delete proxy.a;
proxy.a; // undefined
delete proxy._a; // Error: Can not delete property which start with "_"

defineProperty(target, property, descriptor)

在定义代理对象某个属性时的属性描述时触发该操作,如Object.defineProperty()Object.defineProperties()

  • 如果目标对象不可扩展, 将不能添加属性;

  • 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话;

  • 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的;

  • 如果一个属性在目标对象中存在对应的属性,那么Object.defineProperty(target, prop, descriptor)将不会抛出异常;

  • 在严格模式下,false作为handler.defineProperty方法的返回值的话将会抛出 TypeError

const proxy = new Proxy({}, {
  defineProperty(target, prop, descriptor) {
    console.log('called: ' + prop);
    return Object.defineProperty(target, prop, descriptor);
  }
});

Object.defineProperty(proxy, 'a', { 
  configurable: false, 
  enumerable: true, 
  value: 10, 
});

Object.defineProperty(proxy, 'a', { 
  configurable: true, 
}); // TypeError: Cannot redefine property: a

has(target, property)

在判断代理对象是否拥有某个属性时触发该操作,如in运算符。

  • 如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏

  • 如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏

const proxy = new Proxy({
  a: 1,
  _a: 2,
}, {
  has(target, prop) {
    if (prop.startsWith('_')) {
      return false;
    }
    return prop in target;
  }
});
console.log('a' in proxy); // true
console.log('_a' in proxy); // false

const obj = { a: 10 };
Object.preventExtensions(obj);
const proxy2 = new Proxy(obj, {
  has(target, prop) {
    return false;
  }
});
console.log('a' in proxy2); 
// TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible

has拦截对for...in循环不生效。

const obj = { x: 1, y: 2 };
const proxy = new Proxy(obj, {
  has(target, prop) {
    console.log(prop);
    return false;
  }
});

for (let b in proxy) {
  console.log(proxy[b]);
}
// 1 
// 2

ownKeys(target)

在获取代理对象的所有属性键时触发该操作,如在 Object.getOwnPropertyNames(obj)时。

  • 结果必须是一个数组;

  • 数组的元素类型只能是StringSymbol

  • 结果列表必须包含目标对象的所有不可配置(non-configurable )、自有(own)属性的key;

  • 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(own)属性的key,不能有其它值。

const obj = {};
Object.defineProperty(obj, 'a', { 
  configurable: false, 
  enumerable: true, 
  value: 10 
});

const p = new Proxy(obj, {
  ownKeys(target) {
    return ['a', Symbol.for('b'), 'c'];
  }
});
console.log(Object.getOwnPropertyNames(p)); // [ 'a', 'c' ]

const p2 = new Proxy(obj, {
  ownKeys(target) {
    return ['a', 123, 12.5, true, false, undefined, null, {}, []];
  }
});
console.log(Object.getOwnPropertyNames(p2)); // TypeError: XXX is not a valid property name

const p3 = new Proxy(obj, {
  ownKeys(target) {
    return ['b', 'c'];
  }
});
console.log(Object.getOwnPropertyNames(p3)); 
// TypeError: 'ownKeys' on proxy: trap result did not include 'a'

Object.preventExtensions(obj);
const p4 = new Proxy(obj, {
  ownKeys(target) {
    return ['a', 'b'];
  }
});
console.log(Object.getOwnPropertyNames(p4)); 
// TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible

getOwnPropertyDescriptor(target, property)

在获取代理对象某个属性的属性描述时触发该操作,如:Object.getOwnPropertyDescriptor(obj, prop)

  • 必须返回一个objectundefined

  • 如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在

  • 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在

  • 如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在

  • 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在

  • Object.getOwnPropertyDescriptor(target)的结果可以使用Object.defineProperty应用于目标对象,也不会抛出异常

const obj = { a: 20 };
Object.defineProperty(obj, 'b', { 
  configurable: false, 
  enumerable: true, 
  value: 20 
});


var p = new Proxy(obj, {
  getOwnPropertyDescriptor(target, prop) {
    return { configurable: true, enumerable: true, value: 10 };
  }
});
console.log(Object.getOwnPropertyDescriptor(p, 'a').value);


var p2 = new Proxy(obj, {
  getOwnPropertyDescriptor(target, prop) {
    return [];
  }
});
console.log(Object.getOwnPropertyDescriptor(p2, 'b'));
// TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property 'a' which is either non-existant or configurable in the proxy target

var p3 = new Proxy(obj, {
  getOwnPropertyDescriptor(target, prop) {
    return undefined;
  }
});
console.log(Object.getOwnPropertyDescriptor(p3, 'b'));
// TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned undefined for property 'a' which is non-configurable in the proxy target

apply(target, context, args)

在调用一个目标对象为函数的代理对象时触发该操作,比如 proxy()proxy.call()proxy.apply()

target必须可被调用

function target(...args) {
    return args.reduce((sum, item) => sum + item, 0);
}

const proxy = new Proxy(target, {
    apply(target, context, args) {
        console.log(context);
        return target(...args) - 1;
    }
});

proxy(1, 2, 3); // undefined 5
proxy.call(null, 1, 2, 3); // null 5
proxy.apply(window, [1, 2, 3]); // window 5

construct (target, args, newTarget)

target必须是一个合法的 constructor ,并且必须返回一个对象

const proxy = new Proxy(function () {}, {
  construct: function(target, args, newTarget) {
    return { value: args[0] * 10 };
  }
});
(new proxy(1)).value; // 10

const proxy2 = new Proxy(function () {}, {
  construct: function(target, args, newTarget) {}
});
new proxy2(1); // TypeError: 'construct' on proxy: trap returned non-object ('undefined')

const proxy3 = new Proxy({}, {
  construct: function(target, args, newTarget) {
    return { value: args[0] * 10 };
  }
});
new proxy3(1); // TypeError: proxy3 is not a constructor

getPrototypeOf(proxy)

在读取代理对象的原型时触发该操作,比如在执行Object.getPrototypeOf(proxy)时。

const proxy = new Proxy({}, {
    getPrototypeOf(target) {
      return Array.prototype;
    }
});

Object.getPrototypeOf(proxy) === Array.prototype; // true
proxy.__proto__ === Array.prototype; // true
proxy.isPrototypeOf(Array.prototype); // true
console.log(proxy instanceof Array); // true

当同时触发处理器getgetPrototypeOf操作时,get的优先级更高。

const proxy = new Proxy({}, {
    getPrototypeOf(target) {
      return Array.prototype;
    },
    get(target, property) {
      return null;
    }
});

Object.getPrototypeOf(proxy) === Array.prototype; // true
proxy.__proto__ === Array.prototype; // false
proxy.isPrototypeOf(Array.prototype); // TypeError: proxy.isPrototypeOf is not a function
console.log(proxy instanceof Array); // true

setPrototypeOf(target, proto)

在设置代理对象的原型时触发该操作,如:Object.setPrototypeOf(proxy)。如果返回false会抛出个TypeError异常。

如果target不可扩展,原型参数必须与Object.getPrototypeOf()的值相同。

const proxy = new Proxy(function () {}, {
    setPrototypeOf(target, proto) {
        return false;
    }
});
Object.setPrototypeOf(proxy, {}); // TypeError: 'setPrototypeOf' on proxy: trap returned falsish

const target = {};
Object.preventExtensions(target);
const proxy2 = new Proxy(target, {
    setPrototypeOf(target, proto) {
        return true;
    }
});
Object.setPrototypeOf(proxy2, {}); 
// TypeError: 'setPrototypeOf' on proxy: trap returned truish for setting a new prototype on the non-extensible proxy target

isExtensible(target)

在判断一个代理对象是否是可扩展时触发该操作,如:Object.isExetensible(proxy)时。

Object.isExtensible(proxy) 必须返回与Object.isExtensible(target)相同的值。

const p = new Proxy({}, {
  isExtensible(target) {
    return true;
  }
});
Object.isExtensible(p); // true

const obj = {};
Object.preventExtensions(obj);
const p1 = new Proxy(obj, {
  isExtensible(target) {
    return true;
  }
});
const p2 = new Proxy(obj, {
  isExtensible(target) {
    return false;
  }
});
Object.isExtensible(p1); // TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'false')
Object.isExtensible(p2); // false

preventExtensions(target)

在让一个代理对象不可扩展时触发该操作,比如在执行Object.preventExtensions(proxy)时。如果返回false会抛出个TypeError异常。

如果Object.isExtensible(proxy)falseObject.preventExtensions(proxy)只能返回true

const p = new Proxy({}, {
  preventExtensions(target) {
    Object.preventExtensions(target);
    return false;
  }
});

Object.preventExtensions(p) // TypeError: 'preventExtensions' on proxy: trap returned falsish

最后更新于