ES6 自定义JavaScript语言行为的 Proxy 对象

 2016年05月18日    140     声明


Proxy(代理)对象用于自定义JavaScript基本操作的形为(如:属性查找、赋值、枚举、函数调用等),该对象是JavaScript语言标准ES 6ECMAScript 2015)中新增的对象,通过该对象使我们具有了对JavaScript语言层面进行修改的能力。


  1. Proxy语法说明
  2. Proxy.revocable()方法
  3. handler对象方法
  4. 使用示例

1. Proxy语法说明

var p = new Proxy(target, handler);

参数

  • target-被代理的一个目标对象(可以是任何类型的对象、数组、函数,甚至是另一个代理)
  • handler-处理对象,其属性是定义代理时的行为函数,在目标对象上执行的代理操作都会被些函数处理
  • 返回值-代理对象实例

Proxy即“代理”,我们通过new Proxy()构造函数,来对target设置处理对象handler。初始化后会返回一个代理实例对象p,该对象是target的代理器,访问target对象的形为都会首先经过该代理器处理。代理器会判断这种形为有没有在handler中定义,如果已定义则由代理形为处理,如果未定义则由target处理。


2. Proxy.revocable()方法

2.1 revocable()语法

Proxy.revocable(target, handler);

创建一个可撤销的Proxy对象。

可撤销的Proxy对象允有{proxy: proxy, revoke: revoke}两个属性:

  • proxy-通过调用new Proxy(target, handler)创建的代理对象
  • handler-一个没有参数的函数,用于撤销proxy

revoke()方法调用后,代理会变为无效状态,handler上的任何trap都会返回TypeError。该方法不可重复调用。


2.2 revocable()方法的使用

var revocable = Proxy.revocable({}, {
  get: function(target, name) {
    return "[[" + name + "]]";
  }
});
var proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"

// 撤销代理
revocable.revoke();

console.log(proxy.foo); // 抛出 TypeError
proxy.foo = 1           // 再次抛出 TypeError
delete proxy.foo;       // 仍然是 TypeError
typeof proxy            // "object"


3. handler对象方法

handler是一个包含陷入(trap)代理的占位符函数。

所有陷入指令都是可选的,如果陷入指令未定义那么会继续定向到target对象来处理。所有陷入指令如下:

  • handler.getPrototypeOf() - Object.getPrototypeOf的陷入指令(代理)
  • handler.setPrototypeOf() - Object.setPrototypeOf的陷入指令(代理)
  • handler.isExtensible() - Object.isExtensible的陷入指令(代理)
  • handler.preventExtensions() - Object.preventExtensions的陷入指令(代理)
  • handler.getOwnPropertyDescriptor() - Object.getOwnPropertyDescriptor的陷入指令(代理)
  • handler.defineProperty() - Object.defineProperty的陷入指令(代理)
  • handler.has() - in操作符的陷入指令(代理)
  • handler.get() - Object.get的陷入指令(代理)
  • handler.set() - Object.set的陷入指令(代理)
  • handler.deleteProperty() - delete操作符的陷入指令(代理)
  • handler.ownKeys() - Object.getOwnPropertyNames的陷入指令(代理)
  • handler.apply() - function调用的陷入指令(代理)
  • handler.construct() - new操作符(构造函数)的陷入指令(代理)


4. 使用示例

自定义访问器

定义get代理,当用户访问属性不存在时返回'不存在'

var handler = {
    get: function(target, name){
        return name in target?
            target[name] :
            '不存在';
    }
};

var 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, '不存在'

当不定义具体代理形为时,形为会继续交由对象本身处理:

var target = {};
var p = new Proxy(target, {});

p.a = 37; // 由对象本身的set处理operation forwarded to the target

console.log(target.a); // 37. 由对象本身的get处理

扩展构造函数

一个函数代理可以很容易地用一个新的构造函数扩展构造函数。扩展构造函数时,需要constructapply两个处理器:

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

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

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

Boy.prototype.sex = "M";

var Peter = new Boy("Peter", 13);
console.log(Peter.sex);  // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age);  // 13