学习Proxy(代理)和Reflect(反射)

Proxy

一、Proxy

  1. MDN 的描述:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
  2. 红宝书的描述:代理是目标对象的抽象。从很多方面看,代理类似 C++指针,因为它可以用作目标对象的替身,但又完全独立于目标对象。目标对象既可以直接被操作,也可以通过代理来操作。但直接操作会绕过代理施予的行为。
  3. 我自己在看的一个ES6的文档:Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
    下面是定义的步骤
const target = { 
  id: 'target',
}; 
const handler = {
  get: function (target, key, receiver) {
  console.log(`getting ${key}!`);
  return Reflect.get(target, key, receiver);
 },
};
const proxy = new Proxy(target, handler); 
//可以通过proxy 来访问源对象上面的数据 和 方法
// id 属性会访问同一个值 
console.log(target.id); // target 
console.log(proxy.id); // getting id!  target
console.log(proxy.uuid); // getting uuid!  undefined

二、Proxy 的主要目的于用途

  1. 使用代理的主要目的是可以定义捕获器(trap)。捕获器就是处理程序对象中定义的“基本操作的拦截器”。(红宝书P267 9.1.2)。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。(上面的修改的GET方法)
  2. 和Reflect 一起使用,好像在VUE3.0中,就是用Proxy和Reflect一起实现数据的跟新的(可见视频教程)。有一说一,天禹老师讲的很基础,对我这种只会用,不了解原理的人帮助挺大的。
  3. 跟踪属性访问
    通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获
    器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过
const user = { 
  name: 'Jake' 
}; 
const proxy = new Proxy(user, { 
  get(target, property, receiver) { 
  console.log(`Getting ${property}`); 
  return Reflect.get(...arguments); 
 }, 
  set(target, property, value, receiver) { 
  console.log(`Setting ${property}=${value}`); 
  return Reflect.set(...arguments); 
  } 
}); 
proxy.name; // Getting name 
proxy.age = 27; // Setting age=27
  1. 隐藏属性
    代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。比如:
const hiddenProperties = ['foo', 'bar']; 
const targetObject = { 
 foo: 1, 
 bar: 2, 
 baz: 3 
}; 
const proxy = new Proxy(targetObject, { 
  get(target, property) { 
  if (hiddenProperties.includes(property)) { 
    return undefined; 
  } else { 
    return Reflect.get(...arguments); 
  } 
 }, 
 has(target, property) { 
   if (hiddenProperties.includes(property)) { 
     return false; 
   } else { 
    return Reflect.has(...arguments); 
   } 
 } 
}); 
// get() 
console.log(proxy.foo); // undefined 
console.log(proxy.bar); // undefined 
console.log(proxy.baz); // 3 
// has() 
console.log('foo' in proxy); // false 
console.log('bar' in proxy); // false 
  1. 属性验证
    因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:
const target = { 
  onlyNumbersGoHere: 0 
}; 
const proxy = new Proxy(target, { 
  set(target, property, value) { 
    if (typeof value !== 'number') { 
      return false; 
    } else { 
      return Reflect.set(...arguments); 
    } 
  } 
}); 
proxy.onlyNumbersGoHere = 1; 
console.log(proxy.onlyNumbersGoHere); // 1 
proxy.onlyNumbersGoHere = '2'; 
console.log(proxy.onlyNumbersGoHere); // 1 
  1. 函数与构造函数参数验证
    跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。比如,可以让函数只接收某种类型的值:
function median(...nums) { 
  return nums.sort()[Math.floor(nums.length / 2)]; 
} 
const proxy = new Proxy(median, { 
  apply(target, thisArg, argumentsList) { 
  for (const arg of argumentsList) { 
    if (typeof arg !== 'number') { 
      throw 'Non-number argument provided'; 
    } 
  } 
  return Reflect.apply(...arguments); 
 } 
}); 
console.log(proxy(4, 7, 1)); // 4 
console.log(proxy(4, '7', 1)); 
// Error: Non-number argument provided
  1. 实现观察者模式
    观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
const person = observable({
  name: '张三',
  age: 20
});
function print() {
  console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';// 李四, 20
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach(observer => observer());
  return result;
}

三、取消Proxy 与存在的问题

Proxy.revocable方法返回一个可取消的Proxy实例。大致步骤如下

let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
  1. this 问题
    代理潜在的一个问题来源是 this 值。我们知道,方法中的 this 通常指向调用这个方法的对象,但是在Proxy代理的时候,this指向关键字是指向Proxy代理的:
const _name = new WeakMap();
class Person {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this);
  }
}
const jane = new Person('Jane');
jane.name // 'Jane'
const proxy = new Proxy(jane, {});
proxy.name // undefined

上面代码中,目标对象jane的name属性,实际保存在外部WeakMap对象_name上面,通过this键区分。由于通过proxy.name访问时,this指向proxy,导致无法取到值,所以返回undefined。

  1. 代理与内部槽位问题(红宝书上中提出)
    代理与内置引用类型(比如 Array)的实例通常可以很好地协同,但有些 ECMAScript 内置类型可能会依赖代理无法控制的机制,结果导致在代理上调用某些方法会出错。一个典型的例子就是 Date 类型。根据 ECMAScript 规范,Date 类型方法的执行依赖 this 值上的内部槽位[[NumberDate]]。代理对象上不存在这个内部槽位,而且这个内部槽位的值也不能通过普通的 get()和 set()操作访问到,于是代理拦截后本应转发给目标对象的方法会抛出 TypeError:
const target = new Date(); 
const proxy = new Proxy(target, {}); 
console.log(proxy instanceof Date); // true 
proxy.getDate(); // TypeError: 'this' is not a Date object

四、提前终止生成器

与迭代器类似,生成器也支持“可关闭”的概念。一个实现 Iterator 接口的对象一定有 next()方法,还有一个可选的 return()方法用于提前终止迭代器。生成器对象除了有这两个方法,还有第三个方法:throw()。

  1. return()方法会强制生成器进入关闭状态。提供给 return()方法的值,就是终止迭代器对象的值。
  2. throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭 。

Reflect

一、Reflect

自己的理解:和Proxy很像,但是不能通过new关键字生成。
MDN:Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers (en-US)的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
使用例子

const duck = {
  name: 'Maurice',
  color: 'white',
  greeting: function() {
    console.log(`Quaaaack! My name is ${this.name}`);
  }
}
Reflect.has(duck, 'color');// true
Reflect.has(duck, 'haircut');// false
Reflect.ownKeys(duck);//查询属性
// [ "name", "color", "greeting" ]
Reflect.set(duck, 'eyes', 'black');//添加属性
// returns "true" if successful
// "duck" now contains the property "eyes: 'black'"
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值