3 - Proxy

166 阅读4分钟

知识点:

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

语法:const p = new Proxy(target, handler)

  • target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。 (容器, 无数可以处理对象属性的方法;)

二、defineProperty和Proxy的区别:

  1. defineProperty()劫持数据,是给对象进行扩展,对属性进行设置;
  2. var obj = {},defineProperty(obj),defineProperty是直接处理obj,对obj进行操作的时候,它会在get()、set()方法里面进行拦截;Proxy()是通过处理obj之后返回一个代理函数(对象),是通过操作代理对象来处理数据;即:defineproperty是去修改原对象,修改原对象的属性,而proxy只是对原对象进行代理并给出一个新的代理对象,不会污染原对象;
  3. defineProperty()只可以操作对象;Proxy()可以操作对象、数组、函数,可以通过handler重写proxy的内部方法;
  4. defineProperty和proxy都具有拦截数据的功能,但是defineProperty的作用是给对象增加属性;
  5. 相较于Object.defineProperty劫持某个属性,Proxy则更彻底,不在局限某个属性,而是直接对整个对象进行代理; 也就是Proxy会省去for in 循环提高了效率;
  6. 对象上定义新属性(对象属性的添加或删除)时,Proxy可以监听到,Object.defineProperty监听不到。
  7. 数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
  8. Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。
特性definePropertyProxy
1. 兼容性支持主流浏览器(IE8及以上)不支持IE
2. 操作时是否对原对象直接操作是(会污染原对象)否(需要对Proxy实例进行操作)
3. 可劫持的操作get、setget、set、defineProperty、has、apply等13种
4. 是否可以劫持整个对象否(只能通过遍历的方式)
5. 是否可监听数组变化否(vue中对数组的几种常用方法进行了hack)
6. 是否具有拦截数据的功能

1. 自定义对象属性的获取、赋值、枚举、函数调用等功能

Proxy直接代理了target整个对象,并且返回了一个新的对象,通过监听代理对象上属性的变化来获取目标对象属性的变化;Proxy能够监听到属性的增加、删除

let target = {
  a: 1,
  b: 2
}
let proxy = new Proxy(target, {
  get: function (target, prop, receiver) {
    return target[prop];
    //return Reflect.get(target, prop, receiver);
  },
  set: function (target, prop, value, receiver) {
    target[prop] = value;
    //return Reflect.set(target, prop, value, receiver);
  },
  deleteProperty: function (target, prop) {
    console.log(`delete ${prop}!`);
    delete target[prop];
    return true;
  }
})
console.log('proxy.a', proxy.a); // 1
console.log('target.a', target.a); // 1

proxy.a = 2;
proxy.b = 3;
console.log('target', target); //{ a: 2,b: 3}
console.log('proxy.a', proxy.a); // 2

delete proxy.a; //删除a ---> deleteProperty(){} -> true

2. Proxy操作数组

不管是数组下标或者数组长度的变化,还是通过函数调用,Proxy都能很好的监听到变化;而且除了我们常用的get、set,Proxy更是支持13种拦截操作。

let arr = [{
  name: '小明',
  age: '18'
}, {
  name: '小红',
  age: '23'
}, {
  name: '小青',
  age: '16'
}, {
  name: '小蓝',
  age: '28'
}, {
  name: '小紫',
  age: '8'
}];
let persons = new Proxy(arr, {
  get(arr, prop) {
    console.log(`getting ${prop}!`);
    return arr[prop];
  },
  set(arr, prop, value) {
    console.log(`setting ${prop}:${value}!`);
    arr[prop] = value;
  }
})
console.log(persons[3]);
persons[1] = {
  name: '小猫',
  age: '20'
}
console.log(persons, arr)

3. Proxy操作函数

let fn = function () {
  console.log("function");
}
fn.a = 123;
let newFn = new Proxy(fn, {
  get(fn, prop) {
    console.log('fn[prop]', fn[prop])
    return fn[prop];
  },
  set(fn, prop, value) {
    fn[prop] = value;
    console.log('fn[prop]', fn[prop]);
  }
})
newFn.a = 456;
console.log(newFn.a);

4. 重写Proxy方法

function J_Proxy(target, handler) {
  let _target = deepClone(target); //深克隆目标对象
  //console.log(Object.keys(_target));
  
  //获取键集合Object.keys(obj)
  Object.keys(_target).forEach((key) => {
    Object.defineProperty(_target, key, {
      get() {
        return handler.get && handler.get(target, key);
      },
      set(newVal) {
        return handler.set && handler.set(target, key, newVal);
      }
    });
  });
  return _target;

  //深克隆
  function deepClone(origin, target) {
    let tar = target || {},
        toStr = Object.prototype.toString,
        arrType = '[object Array]'; // 数组类型
    for (let key in origin) {
      //过滤掉原型上的属性,只克隆对象本身的属性
      if (origin.hasOwnProperty(key)) {
        //判断要拷贝的值的类型是引用值还是原始值,还要排除null,因为typeof(null) -> object
        if (typeof (origin[key]) === 'object' && origin[key] !== null) {
          toStr.call(origin[key]) === arrType ? [] : {};
          deepClone(origin[key], tar[key]);
        } else {
          //原始值
          tar[key] = origin[key];
        }
      }
    }
    return tar;
  }
}

//调用重写的Proxy方法
let target1 = {
  a: 1,
  b: 2
}
let proxy1 = new J_Proxy(target, {
  get(target, prop) {
    return target[prop];
  },
  set(target, prop, value) {
    target[prop] = value;
    console.log('value', value)
  }
})
console.log(proxy1.a); //2
proxy1.b = 3;

5. proxy的内部方法

let target2 = {
  a: 1,
  b: 7
}
let proxy2 = new Proxy(target2, {
  get(target, prop) {
    return target[prop];
  },
  set(target, prop, value) {
    return target[prop] = value;
  },
  has(target, prop) {
    console.log(target[prop]);
  },
  deleteProperty(target, prop) {
    delete target[prop];
    console.log(1);
  }
})
console.log('a' in proxy); // true
console.log(proxy2); // { a: 1,b: 7}
delete proxy2.a;
console.log(proxy2); // {b: 7}

6. Reflect.set 以函数的方式在对象上设置属性。

let target3 = {
  a: 1,
  b: 2
}
let proxy3 = new Proxy(target3, {
  get(target, prop) {
    return Reflect.get(target, prop);
  },
  set(target, prop, value) {
    Reflect.set(target, prop, value);
  }
});
console.log(proxy3.a);
proxy3.b = 4;
console.log(proxy3); // { a:1,b:4}