ES6 Proxy 与 ES6 Reflect
注意此方法不支持 IE
反射是什么意思?
Reflect 是内置对象。
举个例子,我们这里接触到的主要是代理之后获取属性
常见的获取属性有三种
•Object.key•Object['key']•Reflect.get(Object,key)
也就是
•点表示(dot notation)•括号表示(bracket notation)•Reflect 反射
Proxy
`let appleData = {`
`discount: 0.9,`
`price: 10,`
`};`
`// 对数据的代理`
`let proxiedAppleData = new Proxy(appleData, {});`
`console.log(proxiedAppleData.price); // 10`
`var p = new Proxy(target, handler);`
`var p = new Proxy(target, {`
`get: function (target, property, receiver) {},`
`});`
Proxy target
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
Proxy handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
Proxy handler property
被获取的属性名。
Proxy handler receiver
Proxy 或者继承 Proxy 的对象
`let appleData = {`
`discount: 0.9,`
`price: 10,`
`};`
`// 对数据的代理`
`let proxiedAppleData = new Proxy(appleData, {`
`get(target, key) {`
`console.log("GET", key);`
`return target[key];`
`},`
`});`
`console.log(proxiedAppleData.price); // 10`
Reflect.get(target, propertyKey[, receiver])
Reflect.get target
需要取值的目标对象
Reflect.get handler
需要获取的值的键值
Reflect.get receiver
如果 target 对象中指定了 getter,receiver 则为 getter 调用时的 this 值
使用 Reflect 改造例子
`let appleData = {`
`discount: 0.9,`
`price: 10,`
`};`
`// 对数据的代理`
`let proxiedAppleData = new Proxy(appleData, {`
`get(target, key, receiver) {`
`console.log("GET", key);`
`// 确保this指向的问题`
`return Reflect.get(target, key, receiver);`
`},`
`});`
`console.log(proxiedAppleData.price); // 10`
加入 set 方法
`let appleData = {`
`discount: 0.9,`
`price: 10,`
`};`
`// 对数据的代理`
`let proxiedAppleData = new Proxy(appleData, {`
`get(target, key, receiver) {`
`console.log("GET", key);`
`// 确保this指向的问题`
`return Reflect.get(target, key, receiver);`
`},`
`set(target, key, value, receiver) {`
`console.log("SET", key, value);`
`return Reflect.set(target, key, value, receiver);`
`},`
`});`
`console.log(proxiedAppleData.price); // 10`
小课堂
为什么这里必须要传一个 receiver ?
简单介绍下
自己写例子的时候,发现不传 receiver 完全不会出问题的,是写法冗余了吗,其实不是写法冗余,是测试用例覆盖不全导致的
我们来看这个例子
receiver 表示的是 this 的指向
`var target = {`
`get a() {`
`return this.c;`
`},`
`};`
`Reflect.get(target, "a", { c: 4 }); // 4`
Receiver 指向 proxy 本身或者继承他的对象
`var target = {`
`get a() {`
`return this.b;`
`},`
`};`
`var proxy = new Proxy(`
`{},`
`{`
`get: function (target, property, receiver) {`
`console.log(this);`
`return receiver;`
`},`
`}`
`);`
`proxy.getReceiver; // proxy`
`var inherits = Object.create(proxy);`
`inherits.getReceiver; // inherits`
这里的的 get 中的 this 指向的是 handler 处理器
这些都很完美,那么出问题的例子长什么样子尼
`var target = {`
`get a() {`
`return this.b;`
`},`
`};`
`var p = new Proxy(target, {`
`get(raw, key, receiver) {`
`if (key === "b") {`
`return 3;`
`}`
`// receiver就是 proxy 对象,这下应该能访问到 'b' 了吧`
`return Reflect.get(receiver, key);`
`},`
`});`
`p.a; // 这里直接会报错,我们来分析下为什么`
首先 p.a 会被 handler 处理 进入 get 会发生什么事情 return handler 的 b 属性 // Reflect.get(target,key,this) 大概是这样子 Reflect.get(receiver, key); 目标是 this key 是 a
会继续去访问 a 属性 如此形成了无限循环 最初溢出报错
如果加上 Reflect.get(target,key,this) 三个参数给全
`var target = {`
`get a() {`
`return this.b;`
`},`
`};`
`var p = new Proxy(target, {`
`get(raw, key, receiver) {`
`if (key === "b") {`
`return 3;`
`}`
`// receiver就是 proxy 对象,这下应该能访问到 'b' 了吧`
`return Reflect.get(raw, key, receiver);`
`},`
`});`
`p.a; // 3`
我们来分析下流程 首先 p.a 会被 handler 处理 然后 return Reflect.get(raw, key, receiver); raw 是一开始的 target key 是 'a' receiver 是 p 本身 等通过 get 操作后 key 变成了 'b'
Call Stack 目前的状态是
[ //顶部 get, // 这是去查找 key 为 b 的 get get a, // 这里的是 target 中的 get a get, // 这里是 return Reflect 中的 get 方法 ... ]
所以这边是可以获取到值为 3 的
其实 vue3 中使用 proxy 时候对数组也是做了特殊的处理的,如果想当然的写下面这种代码
`const p = new Proxy([1, 2, 3], {`
`get(target, key, receiver) {`
`return target[key];`
`},`
`set(target, key, value, receiver) {`
`target[key] = value;`
`},`
`});`
`p.push(100); // 会报错运行不起来`
但是改成
`const p = new Proxy([1, 2, 3], {`
`get(target, key, receiver) {`
`console.log("get: ", key);`
`return Reflect.get(target, key, receiver);`
`},`
`set(target, key, value, receiver) {`
`console.log("set: ", key, value);`
`return Reflect.set(target, key, value, receiver);`
`},`
`});`
`p.push(100);`
`// get: push`
`// get: length`
`// set 3 100`
`// set length 4`
`// 从上面的代码可以发现执行 push 操作时,还会访问 length 属性。推测执行过程如下:根据 length 的值,得出最后的索引,再设置新的置,最后再改变 length。`
通过 Reflect 可以获取默认行为 所以配合使用确实效果更佳,其实上面的代码去掉 receiver 也是可以运行的 结果不会变的,为什么要加上 receiver 之前的例子解释过了