Javascript:Proxy对象

352 阅读3分钟

Proxy

Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义,如属性查找赋值枚举函数调用等。

const p = new Proxy(target, handler)

target: 要使用Proxy包装的目标对象。可以是任何类型的对象,包括原生数组、函数,甚至是另一个代理。
handler:通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为。

方法

Proxy方法

Proxy.revocable(target, handler) 创建一个可撤销的Proxy对象。

const revocable = Proxy.revocable({}, {
  get(target, name) {
    return `[[${name}]]`
  }
});

// revocable返回对象 {proxy, revoke}
// proxy 表示新生成的代理对象本身;
// revoke 表示撤销方法,调用的时候不需要加任何参数

const proxy = revocable.proxy;
proxy.foo; // "[[foo]]"

revocable.revoke();

console.log(proxy.foo);  // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1;  // TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo;  // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
typeof proxy;   // 'object', 因为typeof不属于可代理操作

handler对象的方法

handler对象是容纳一批特定属性的占位符对象,包含有Proxy各个捕获器。所有的捕获器是可选的,如果没有定义某个捕获器,就会保留源对象的默认行为。(handler对象方法篇幅较长,在文末附文)

示例

基础示例

const handler = {
  get(obj, prop) {
    return prop in obj ? obj[prop] : 37;
  }
}

const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);   //   1 undefined
console.log('c' in p, p.c);    //   false 37

无操作转发代理

const target = {};
const p = new Proxy(target, {});
p.a = 37;
console.log(target.a);   // 37

验证

const validator = {
  set(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not a integer!');
      }
      
      if (value > 200) {
        throw new RangeError('The age invalid!');
      }
    }
    
    obj[prop] = value;
    return true;
  }
}

const proxy = new Proxy({}, validator);
proxy.age = 100;
console.log(proxy.age);  // 100;
proxy.age = "aa";  // TypeError: The age is not a integer!
proxy.age = 201;  // RangeError: The age invalid!

扩展构造函数

function extend(sup, base) {
  const descriptor = Object.getOwnPropertyDescriptor(
    base.prototype, "constructor"
  );
  base.prototype = Object.create(sup.prototype);
  
  const handler = {
    construct(target, args) {
      const obj = Object.create(base.prototype);
      this.apply(target, obj, args);
      return obj;
    },
    apply(target, that, args) {
      sup.apply(that, args);
      base.apply(that, args);
    }
  };
  
  const proxy = new Proxy(base, handler);
  descriptor.value = proxy;
  Object.defineProperty(base.prototype, "constructor", descriptor);
  return proxy;
}

const Person = function (name) {
  this.name = name;
}

const Boy = extend(Person, function(name, age) {
  this.age = age;
})

Boy.prototype.sex = "男";

const Peter = new Boy("Peter", 13);
Peter.sex;   // "男"
Peter.name;   // Peter
Peter.age;   // 13

附:handler对象方法

handler.getPrototypeOf() Object.getPrototypeOf 方法的捕获器。

// 返回值必须是对象或者 `null`
const obj = {}
const p = new Proxy(obj, {
  getPrototypeOf(target) {
    return Array.prototype;
  }
});

// 触发 `getPrototypeOf` 的五种方式 
console.log(
  Object.getPrototypeOf(p) === Array.prototype, // true
  Reflect.getPrototypeOf(p) === Array.prototype, // true
  p.__proto__ === Array.prototype, // true
  Array.prototype.isPrototypeOf(p), // true
  p instanceof Array  // true
);


// 返回值不是对象或者 `null`,抛出异常
const obj = {}
const p = new Proxy(obj, {
  getPrototypeOf(target) {
    return 'foo';
  }
});
Object.getPrototypeOf(p);  // TypeError: 'getPrototypeOf' on proxy: trap returned neither object nor null

// 目标对象不可扩展,且 `getPrototypeOf()` 方法返回的原型不是目标对象本身的原型,抛出异常
const obj = Object.preventExtensions({});
const p = new Proxy(obj, {
  getPrototypeOf(target) {
    return {};
  }
})

Object.getPrototypeOf(p);  // TypeError: 'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype

handler.setPrototypeOf(target, prototype) Object.setPrototypeOf 方法的捕获器

// target 被拦截目标对象
// prototype 对象新原型或为null
// 如果成功修改了[[prototype]],返回值为boolean值

const handler = {
  setPrototypeOf(target, newProto) {
    return false
  }
}

const newProto = {}, target = {};
let p1 = new Proxy(target, handler);
Object.setPrototypeOf(p1, newProto);  // TypeError: 'setPrototypeOf' on proxy: trap returned falsish for property 'undefined'
Reflect.setPrototypeOf(p1, newProto);  // false

handler.isExtensible() Object.isExtensible 方法的捕获器

// 返回值必须是Boolean值或可转换成Boolean的值
const p = new Proxy({}, {
  isExtensible(target) {
    console.log('do');
    return true;
  } 
})

Object.isExtensible(p);  // do  true
// Reflect.isExtensible(p);  // do  true

// Object.isExtensible(proxy) 必须同Object.isExtensbile(target)返回值相同。
// 即必须返回true或为true的值,返回false或为false的值都会报错
const p = new Proxy({}, {
  isExtensible(target) {
    return false;
  }
})

Object.isExtensible(p);
Reflect.isExtensible(p);
//TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')

handler.preventExtensions() Object.preventExtensions 方法的捕获器

// 返回布尔值
const p = new Proxy({}, {
  preventExtensions(target) {
    console.log('do');
    Object.preventExtensions(target);
    return true;
  }
})

Object.preventExtensions(p);  // do   Proxy{...}
Reflect.preventExtensions(p);  // do  true

// 对象是可扩展的,抛出异常
const p = new Proxy({}, {
  preventExtensions(target) {
    return true;
  }
})

Object.preventExtensions(p);  
Reflect.preventExtensions(p); 
// TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible


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

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

handler.getOwnPropertyDescriptor(target, prop) Object.getOwnPropertyDescriptor 方法的捕获器

// target 目标对象
// prop 返回属性名称的描述
// 返回值必须是object或undefined

const p = new Proxy({a: 20}, {
  getOwnPropertyDescriptor(target, prop) {
    console.log(`called: ${prop}`);
    return {configurable: true, enumerable: true, value: 10}
  }
});
Object.getOwnPropertyDescriptor(p, 'a').value;
Reflect.getOwnPropertyDescriptor(p, 'a').value;
// called: a    10

// 如果不变量被违反,则抛出异常
const obj = {a: 10};
Object.preventExtensions(obj);

const p = new Proxy(obj, {
  getOwnPropertyDescriptor(target, prop) {
    return undefined;
  }
});

Object.getOwnPropertyDescriptor(p, 'a')
Reflect.getOwnPropertyDescriptor(p, 'a');
// ypeError: 'getOwnPropertyDescriptor' on proxy: trap returned undefined for property 'a' which exists in the non-extensible proxy target

handler.defineProperty(target, property, descriptor) Object.defineProperty 方法的捕获器

// target 目标对象
// property 待检索其属性的属性名
// descriptor 待定义或修改的属性的描述符
// 返回值为Boolean类型的值

const p = new Proxy({}, {
  defineProperty(target, prop, descriptor) {
    console.log(`called: ${prop}`);
    return true;
  }
});

const desc = {configurable: true, enumerable: true, value: 10};
Object.defineProperty(p, 'a', desc);  // called: a    Proxy{...}
Reflect.defineProperty(p, 'a', desc);  // called: a    true
p.a = 111   // called: a


// 如果违背不变量,则抛出异常
const desc = {configurable: false, enumerable: true, value: 10};
Object.defineProperty(p, 'a', desc); 
Reflect.defineProperty(p, 'a', desc);
// TypeError: 'defineProperty' on proxy: trap returned truish for defining non-configurable property 'a' which is either non-existent or configurable in the proxy target


const p = new Proxy({}, {
  defineProperty(target, prop, descriptor) {
    console.log(`called: ${prop}`);
    return false;
  }
});

const desc = {configurable: true, enumerable: true, value: 10};
Object.defineProperty(p, 'a', desc);
// TypeError: 'defineProperty' on proxy: trap returned falsish for property 'a'

handler.has(target, prop) in 操作符的捕获器

// target 目标对象 
// prop 需要检查是否存在的属性
// 返回Boolean值

const obj = {a: 10};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
  has(target, prop) {
    console.log(`called: ${prop}`)
    return true;
  }
});

'a' in p;   // called: a   true
'a' in Object.create(p);   // called: a   true
with(p) {(a);}   // called: a   10
Reflect.has(p, 'a')    // called: a   true


// 如果目标对象某一属性本身不可被配置,代理隐藏该属性,抛出异常
// 如果目标对象为不可扩展对象,代理隐藏该属性,抛出异常
const obj = {a: 10};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
  has(target, prop) {
    return false;
  }
});

'a' in p;  // TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible

handler.get(target, property, receiver) 属性读取操作的捕获器

// target 目标对象
// property 被获取的属性名
// receiver Proxy或继承Proxy的对象
// 返回任意值

const p = new Proxy({}, {
  get(target, property, receiver) {
    console.log(`called: ${property}`);
    return 10;
  }
});
p.a;   // called: a    10
p['a'];   // called: a    10
Object.create(p)['a'];   // called: a    10
Reflect.get(p, 'a');   // called: a    10


// 访问的目标属性不可写以及不可配置时,返回值与该目标属性的值不同,抛出异常
// 访问的目标属性没有配置访问方法时,即get方法是undefined的,返回值不为undefined,抛出异常
const obj = {};
Object.defineProperty(obj, "a", {
  configurable: false,
  enumerable: false,
  value: 10,
  writable: false
});

const p = new Proxy(obj, {
  get(target, prop, receiver) {
    return 20;
  }
});

p.a;  // TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '10' but got '20')

handler.set(target, property, value, receiver) 属性设置操作的捕获器

// target 目标对象
// property 将被设置的属性名或者Symbol
// value 新属性值
// receiver 最初被调用的对象。通常是Proxy本身,但handler的set方法也有可能出现在原型链上,或以其他方式被间接地调用
// 返回Boolean值

const p = new Proxy({}, {
  set(target, prop, value, receiver) {
    target[prop] = value;
    console.log(`set: ${prop}=${value}`);
    return true;
  }
});
'a' in p;  // false
p.a = 10;  // set: a=10;
p['a'] = 10;  // set: a=10;
Object.create(p)['a'] = 10;  // set: a=10;
Reflect.set(p, 'a', 10);   // set: a=10;


// 目标属性不可写及不可配置,值改变时,抛出异常
// 目标属性没有配置存储方法,即[[set]]属性为undefined,设置值时抛出异常
// set()返回false,抛出异常
const obj = {};
Object.defineProperty(obj, "a", {
  configurable: false,
  enumerable: false,
  value: 10,
  writable: false
});

const p = new Proxy(obj, {
  set(target, prop, value, receiver) {
    target[prop] = value;
    return true;
  }
});

p.a = 20;  // TypeError: 'set' on proxy: trap returned truish for property 'a' which exists in the proxy target as a non-configurable and non-writable data property with a different value

handler.deleteProperty(target, property) delete 操作符的捕获器

// target 目标对象
// property 待删除的属性名
// 返回Boolean值

const p = new Proxy({}, {
  deleteProperty(target, prop) {
    console.log(`called: ${prop}`);
    return true;
  }
});

delete p.a;  // called: a
delete p['a'];  // called: a


// 目标对象的属性不可配置,删除该属性时抛出异常
const obj = {};
Object.defineProperty(obj, 'a', {
  configurable: false,
  writable: false,
  value: 20
});

const p = new Proxy(obj, {
  deleteProperty(target, prop) {
    return true;
  }
});

delete p.a;  // TypeError: 'deleteProperty' on proxy: trap returned truish for property 'a' which is non-configurable in the proxy target


handler.ownKeys() Object.getOwnPropertyNames 方法和Object.getOwnPropertySymbols 方法的捕获器

// 返回枚举对象
const p = new Proxy({a: 1, b: 2, c: 3}, {
  ownKeys(target) {
    return ['a', 'd'];
  }
});

Object.getOwnPropertyNames(p);  // ['a', 'd']
Object.keys(p);  // ['a']
Reflect.ownKeys(p);  // ['a', 'd']


const p = new Proxy({[Symbol.for('k')]: 'ss'}, {
  ownKeys(target) {
    return [Symbol.for('k')];
  }
});

Object.getOwnPropertySymbols(p);  // [Symbol(k)]


// ownKeys 的结果必须是数组,否则抛出异常
// 返回的数组元素类型只能是String或Symbol,否则抛出异常
// 结果列表必须包含目标对象的所有不可配置、自有属性的key,否则抛出异常
// 目标对象不可扩展,结果列表必须包含目标对象的所有自有属性的key,不能有其他值,否则抛出异常
const obj = {};
Object.defineProperty(obj, 'a', {
  configurable: false,
  enumerable: true,
  value: 10
});

const p = new Proxy(obj, {
  ownKeys(target) {
    return [1, 2, 3, true, false, null, undefined, {}, []]    // TypeError: 1 is not a valid property name
    // return ['d']    // TypeError: 'ownKeys' on proxy: trap result did not include 'a'
    // return 'a'    // TypeError: CreateListFromArrayLike called on non-object
  }
});

Object.getOwnPropertyNames(p);

handler.apply(target, thisArg, argumentsList) 函数调用操作的捕获器

// target 目标对象
// thisArg 被调用时的上下文对象
// argumentsList 被调用时的参数数组
// 返回任何值

const p = new Proxy(() => {}, {
  apply(target, thisArg, argumentsList) {
    console.log(`called: ${argumentsList.join(', ')}`);
    const [a1, a2, a3] = argumentsList;
    return a1 + a2 + a3;
  }
});

p(1, 2, 3);   // called: 1, 2, 3      6


// target必须是一个函数对象
const p = new Proxy({}, {
  apply(target, thisArg, argumentsList) {
    console.log(`called: ${argumentsList.join(', ')}`);
    const [a1, a2, a3] = argumentsList;
    return a1 + a2 + a3;
  }
});

p(1, 2, 3);   // TypeError: p is not a function

handler.construct(target, argumentsList, newTarget) new 操作符的捕获器

// target 目标对象
// argumentsList constructor的参数列表
// newTarget 最初被调用的构造函数
// 返回一个对象

const p = new Proxy(function() {}, {
  construct(target, argumentsList, newTarget) {
    console.log(`called: ${argumentsList.join(', ')}`);
    return {value: argumentsList[0] * 10}
  }
})

new p(2).value;   // called: 2      20


// 返回值必须是对象,否则抛出异常
// target必须具有有效的conctructor供new操作符调用,否则抛出异常
const p = new Proxy(() => {}, {
  construct(target, argumentsList, newTarget) {
    console.log(`called: ${argumentsList.join(', ')}`);
    return {value: argumentsList[0] * 10}
  }
})

new p(2).value;   // TypeError: p is not a constructor


const p = new Proxy(function() {}, {
  construct(target, argumentsList, newTarget) {
    console.log(`called: ${argumentsList.join(', ')}`);
    return 1
  }
})

new p(2).value;   // TypeError: 'construct' on proxy: trap returned non-object ('1')

参考资料:[MDN](Proxy - JavaScript | MDN (mozilla.org))