Es6 - Proxy 和 Reflect

51 阅读6分钟

1、Proxy

  • Proxy 对象用于创建一个代理对象
  • 拦截对象的基本操作(如属性查找、赋值、枚举、函数调用等)
  • 直接监听对象、并非属性(Object.deleteProperty)、不需要递归
  • 拦截函数的集合
    • 增删改查:get、set、has、deleteProperty
    • 属性相关:defineProperty、getOwnPropertyDescriptor、ownkeys
    • 原型相关:getPrototypeOf、setPrototypeOf
    • apply使用:construct、apply
    • 扩展:isExtensible、preventExtensions
// 生成一个代理对象 proxy
var proxy = new Proxy(
  {
    a: {
      aa: {
        aaa: "ppp",
      },
    },
  },
  {
    get(target, property) {
      console.log(target, property);
      return target[property];
    },
    set(target, key, newValue) {
      if (target[key] === newValue) {
        return;
      }
      target[key] = newValue;
    },
    ...
  }
);

console.log(proxy.a.aa.aaa);

get(target, propKey, receiver):读取对象属性时触发
  • 拦截条件
    • 访问属性: proxy[foo] 和 proxy.bar
    • 访问原型链上的属性: Object.create(proxy)[foo]
    • Reflect.get()
  • 约束
    • 当 target 不可配置,则返回的值必须与该目标属性的值 相同
    • 如果 get方法中无 return 返回值,则返回值必须为 undefined
var obj = {};
Object.defineProperty(obj, "a", {
  //不可配时,被Proxy代理的返回值不等于 value 报错
  configurable: true,
  enumerable: false,
  value: 10, //不可写时,被Proxy代理的返回值不等于 value 时不报错
  writable: false,
});

// 返回一个代理对象
var p = new Proxy(obj, {
  get: function (target, prop) {
    //如果get函数中没有 return 默认返回 undefined
    // return target[prop];
    return 20;
  },
});
console.log(p.a); //20

set( target, propKey, value, receiver ) :设置对象属性时触发
  • 拦截条件
    • 指定属性值:proxy[foo] = bar 和 proxy.foo = bar
    • 指定继承者的属性值:Object.create(proxy)[foo] = bar
    • Reflect.set()
  • 约束
    • target的 属性为不可写数据属性,则不能改变它的值
    • garget 没有配置存储方法,即 [[Set]] 属性的是 undefined,则不能设置值
    • 在严格模式下,如果 set() 方法返回 false,那么也会抛出个 TypeError 异常
var p = new Proxy(
  {},
  {
    set: function (target, prop, value, receiver) {
      //给目标对象的prop属性设置value值
      target[prop] = value;
      return true;
    },
  }
);
console.log("a" in p); // false
p.a = 10;
console.log("a" in p); // true
console.log(p.a); // 10

has(target, propKey):判断对象属性是否存在时触发
  • 拦截条件
    • 属性查询: foo in proxy
    • 继承属性查询: foo in Object.create(proxy)
    • with 检查: with(proxy) { (foo); }
    • Reflect.has()
  • 约束
    • 如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏.
    • 如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏
var p = new Proxy(
  {},
  {
    has: function (target, prop) {
      console.log("called: " + prop);
      return true;
    },
  }
);
console.log("a" in p); // "called: a"   // true

var obj = { a: 10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
  has: function (target, prop) {
    return false;
  },
});
"a" in p; // TypeError is thrown

deleteProperty(target, propKey):删除对象属性时触发
  • 触发条件
    • delete proxy[foo]
    • delete proxy.foo
    • Reflect.deleteProperty()
  • 约束
    • 如果目标对象的属性是不可配置的,那么该属性不能被删除
var p = new Proxy(
  {},
  {
    deleteProperty: function (target, prop) {
      console.log("called: " + prop);
      return true;
    },
  }
);
delete p.a; // "called: a"

ownKeys(target):获取对象 keys 时触发
  • 拦截条件
    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Object.keys() // entries、vlaues
    • Reflect.ownKeys()
  • 约束
    • ownKeys 的结果 必须是一个数组,且 元素必须是String或Symbol
    • 结果列表必须包含目标对象的所有 不可配置、自有属性的key
    • 目标对象不可扩展,返回所有自有属性的key,不能有其它值
var obj = { x: "x", y: "y" };
Object.defineProperty(obj, "z", {
  configurable: false,
  enumerable: false,
  value: "z",
  writable: false,
});
var p = new Proxy(obj, {
  ownKeys: function (target, prop) {
    //1、返回必须为一个数组
    //2、返回的元素必须为string或Symbol( 不能包含其他类型的值 )
    //3、必须按照 Object[type] 对应的方法返回相应的值
    return ["x", "y", "z"];
  },
});
console.log(Object.getOwnPropertyNames(p)); // ["x", "y", "z"]

getOwnPropertyDescriptor(target, propKey):获取对象属性描述时触发
  • 拦截条件
    • Object.getOwnPropertyDescriptor() //getOwnPropertyDescriptors
    • Reflect.getOwnPropertyDescriptor() //getOwnPropertyDescriptors
  • 约束
    • getOwnPropertyDescriptor 必须返回一个 object 或 undefined
    • 目标对象的 不可配置 的属性存在,报错
    • 目标对象不可扩展,报错
var obj = { x: "x", y: "y" };
Object.defineProperty(obj, "z", {
  //如果当前属性不可配置
  //调用getOwnPropertyDescriptor会报错
  configurable: false,
  enumerable: false,
  value: "z",
  writable: false,
});
var p = new Proxy(obj, {
  getOwnPropertyDescriptor: function (target, prop) {
    return { configurable: true, enumerable: true, value: 10 };
  },
});
console.log(Object.getOwnPropertyDescriptor(p, "x"));

var obj = { a: 10 };

//如果目标对象不可扩展:报错
Object.preventExtensions(obj);
var p = new Proxy(obj, {
  getOwnPropertyDescriptor: function (target, prop) {
    return undefined;
  },
});
Object.getOwnPropertyDescriptor(p, "a"); // TypeError is thrown

defineProperty(target, propKey, propDesc):设置对象属性描述时触发
  • 拦截条件
    • Object.defineProperty()
    • Reflect.defineProperty()
    • proxy.property='value'
  • 约束
    • 如果目标对象 不可扩展, 将不能添加属性。
    • 不能修改一个属性为不可配置的目标对象
    • 在严格模式下, 返回值为 false 将会抛出 TypeError 异常.
var p = new Proxy(
  {},
  {
    defineProperty: function (target, prop, descriptor) {
      console.log("called: " + prop);

      //配置成功返回 true
      return true;
    },
  }
);

var desc = { configurable: true, enumerable: true, value: 10 };

Object.defineProperty(p, "a", desc); // "called: a"

getPrototypeOf(target):获取对象原型时触发
  • 拦截条件
    • Object.getPrototypeOf()
    • Reflect.getPrototypeOf()
    • proto
    • Object.prototype.isPrototypeOf()
    • instanceof
  • 约束
    • getPrototypeOf() 方法 返回的 不是对象也不是 null
    • 目标对象是不可扩展的,且 getPrototypeOf() 方法返回的原型不是目标对象本身的原型
var obj = {};
var proto = {};
var handler = {
  getPrototypeOf(target) {
    console.log(target === obj); // true
    console.log(this === handler); // true
    return proto;
  },
};

var p = new Proxy(obj, handler);
console.log(Object.getPrototypeOf(p) === proto); // true

var obj = {};
var p = new Proxy(obj, {
  getPrototypeOf(target) {
    //返回非对象或null时报错
    return "foo";
  },
});
Object.getPrototypeOf(p); // 报错

// 不可扩展的对象,应该返回对象本身的原型
var obj = Object.preventExtensions({});

var p = new Proxy(obj, {
  getPrototypeOf(target) {
    return {};
  },
});
Object.getPrototypeOf(p);

setPrototypeOf(target):设置对象原型时触发
  • 拦截条件
    • Object.setPrototypeOf()
    • Reflect.setPrototypeOf()
  • 约束
    • 如果 target 不可扩展
    • 原型参数必须与Object.getPrototypeOf(target) 的值相同
var handlerReturnsFalse = {
  setPrototypeOf(target, newProto) {
    //返回false代表设置原型失败,会抛出错误
    //如果返回true,代表设置原型成功
    return false;
  },
};

var newProto = {},
  target = {};
var p1 = new Proxy(target, handlerReturnsFalse);
Object.setPrototypeOf(p1, newProto); // throws a TypeError
Reflect.setPrototypeOf(p1, newProto); // returns false

apply(target):调用 call、apply 方法时触发
  • 拦截条件
    • proxy(...args)
    • Function.prototype.apply() 
    • Function.prototype.call()
    • Reflect.apply()
  • 约束
    • target必须是可被调用的函数
var p = new Proxy(function () {}, {
  /* 
   target:目标对象
   thisArg:被调用时的上下文对象
   argumentsList:被调用时的参数数组
  */
  apply: function (target, thisArg, argumentsList) {
    return argumentsList[0] + argumentsList[1] + argumentsList[2];
  },
});

console.log(p(1, 2, 3)); //6

construct(target):实例化函数时触发
  • 拦截条件
    • new proxy(...args)
    • Reflect.construct()
  • 约束
    • 必须返回一个对象.
var p = new Proxy(function () {}, {
  /* 
   target:目标对象
   argumentsList:constructor的参数列表
   newTarget :最初被调用的构造函数,就上面的例子而言是p。
  */
  construct: function (target, argumentsList, newTarget) {
    return { value: argumentsList[0] * 10 };
  },
});

console.log(new p(1).value); // 10

isExtensible(target):判断对象是否可扩展时触发
  • 拦截条件
    • Object.isExtensible()
    • Reflect.isExtensible()
  • 约束
    • 返回一个 Boolean 值表明该对象是否可扩展
// {} 为可扩展
var p = new Proxy(
  {},
  {
    isExtensible: function (target) {
      //也可以return 1;等表示为true的值
      return true;
    },
  }
);

console.log(Object.isExtensible(p)); // true

preventExtensions(target):设置禁止扩展属性时触发
  • 拦截条件
    • Object.preventExtensions()
    • Reflect.preventExtensions()
  • 约束
    • 如果目标对象是可扩展的,那么只能返回 false
var obj = {};

/* 
    禁止扩展的三种方法:
    seal、freeze、preventExtensions
*/
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  preventExtensions: function (target) {
    // 补救措施
    Object.preventExtensions(target);
    return true;
  },
});

/* 
    1、只有目标对象是"禁止扩展",且返回true时不报错
    2、不应不就方案:在返回true之前"进制扩展"处理
*/
Object.preventExtensions(p);

2、Reflect

  • 挂载 object 对象一些内部的操作方法
  • Proxy 与 Reflect的作用
    • Proxy - 负责拦截操作
    • Reflect - 负责执行对象的操作
let People = new Proxy(
  {
    _name: "zky",
    get name() {
      return this._name;
    },
  },
  {
    get: function (target, prop, receiver) {
      return target[prop];
    },
  }
);
let Man = { _name: "zky_man" };
Man.__proto__ = People; // Man继承People
console.log(Man._name); // zky_man
console.log(Man.name); // zky

let People = new Proxy(
  {
    _name: "zky",
    get name() {
      return this._name;
    },
  },
  {
    get: function (target, prop, receiver) {
      return Reflect.get(target, prop, receiver);
    },
  }
);
let Man = { _name: "zky_man" };
Man.__proto__ = People; // Man继承People
console.log(Man._name); // zky_man
console.log(Man.name); // zky_man