Proxy实践


概述

Proxy是ES6推出的一个类,用于给对象架设一层拦截器,但凡要访问修改对象上的值或属性,都必须经过这层拦截器, Proxy也叫代理器, 它代理了对对象的操作。

Proxy

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

const p = new Proxy(target, handler)

参数详见:

target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

基础示例

在以下简单的例子中,当对象中不存在属性名时,默认返回值为 37,下面以此展示 get handler 的使用场景。

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

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

console.log('c' in p, p.c); // false, 37

代理转发示例

代理会将所有应用到它的操作转发到这个源 target 对象上

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

p.a = 37;   // 操作转发到目标

console.log(target.a);   // 37 操作已经被正确地转发

详见MDN文档


Proxy使用示例

<script>
const obj = {
    name: "花花",
    age: 18,
    address: "陕西省西安市",
};

// 创建一个代理的对象进行属性的:增,读,删,等操作
const ObjProxy = new Proxy(obj, {

    //set与get中传入的值说明
    //1.target:原对象
    //2.key:当前属性名
    //3.newValue:修改的新值
    //4.receiver:代理的这个对象

    set: function (target, key, newValue, receiver) {

     // 第一种写法:监听到某个属性名新值就赋值给原对象的某个属性名

     // target[key] = newValue;

     // 第二种写法:也是vue3源码的写法,利用Reflect.set来实现监听新值

     Reflect.set(target, key, newValue);

     console.log(`监听:监听到${target[key]}的值已经被修改成了`, newValue);

    },

 

    get: function (target, key, receiver) {

     console.log(`监听:${key}属性被读取了`);

     // 第一种写法

     // return target[key];

     // 第二种写法

     return Reflect.get(target, key, receiver);

    },

   });

   //对这个代理对象进行值修改

   ObjProxy.name = "小红";

   //读取原对象看看是否修改成功

   console.log(obj);

  </script>



Object.defineProperty和proxy的区别

两者常被用于数据拦截,其区别如下:

Object.defineProperty 是一个老方法,兼容性好,Proxy是新方法,有更好的性能和功能,但不兼容IE,也没有polyfill;

Object.defineProperty 是通过修改原对象上的属性来实现数据拦截, 而Proxy是在原对象上加一层拦截,并不会修改原对象;

Object.defineProperty 功能单一,如:无法监听数组变化等;Proxy功能强大,是对整个对象进行拦截;


Object.defineProperty的问题有三个:

  1. 不能监听数组变化,如:数组的 push、pop、shift、unshift、splice、sort、reverse等方法都不能触发set,Vue是对其变异方法进行了重写。
  2. 必须遍历对象的每个属性,并对其设置 set、get 方法;
  3. 当一个对象有深层嵌套时,必须逐层遍历所有属性,直到每个对象都调用到Object.defineProperty为止
Object.keys(obj).forEach(key => {
   Object.defineProperty(obj, key, {
      ...
   })
})


Proxy优势

1.0> 支持数组,不需要对数组方法进行重载;

Proxy代理整个对象,省略了使用 Object.keys() 逐个遍历所有属性的过程;

Proxy只是代理原对象,并未修改原对象;因此在Proxy中可调用原对象本身的方法和属性;

let arr = [1,2,3]
let proxy = new Proxy(arr, {
    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.get(target, key, value, receiver)
    }
})

proxy.push(4)
// 打印内容
4 get push
4 get length
8 set 3 4
8 set length 4

2.0> 嵌套支持:Get里面递归调用proxy并返回

let obj = { a:[1, 2, 3], b:1 }
let handler = {
    get(target, key, receiver) {
        console.log('get', key)
        if(typeof target[key] === 'object' && target[key] !== null)
           return new Proxy(target[key], handler)
        return Reflect.get(target, key, receiver)
    },
   set(target, key, value, receiver) {
       console.log('set', value)
       return Reflect.set(target, key, value, receiver)
   }
}

let proxy = new Proxy(obj, handler)
proxy.b = 8
proxy.a.push(4)


Reflect是一个内置对象,提供拦截js的方法;即,通过Reflect可以直接调用某个对象上的属性或方法,同时 Reflect 具有功能强大的方法。
Polyfill 是一块代码,用来为旧浏览器提供它没有原生支持的较新的功能。
举报

© 著作权归作者所有


0