知识点:
一、Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
语法:const p = new Proxy(target, handler)
- target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。 (容器, 无数可以处理对象属性的方法;)
二、defineProperty和Proxy的区别:
- defineProperty()劫持数据,是给对象进行扩展,对属性进行设置;
- var obj = {},defineProperty(obj),defineProperty是直接处理obj,对obj进行操作的时候,它会在get()、set()方法里面进行拦截;Proxy()是通过处理obj之后返回一个代理函数(对象),是通过操作代理对象来处理数据;即:defineproperty是去修改原对象,修改原对象的属性,而proxy只是对原对象进行代理并给出一个新的代理对象,不会污染原对象;
- defineProperty()只可以操作对象;Proxy()可以操作对象、数组、函数,可以通过handler重写proxy的内部方法;
- defineProperty和proxy都具有拦截数据的功能,但是defineProperty的作用是给对象增加属性;
- 相较于Object.defineProperty劫持某个属性,Proxy则更彻底,不在局限某个属性,而是直接对整个对象进行代理; 也就是Proxy会省去for in 循环提高了效率;
- 对象上定义新属性(对象属性的添加或删除)时,Proxy可以监听到,Object.defineProperty监听不到。
- 数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
- Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。
| 特性 | defineProperty | Proxy |
|---|---|---|
| 1. 兼容性 | 支持主流浏览器(IE8及以上) | 不支持IE |
| 2. 操作时是否对原对象直接操作 | 是(会污染原对象) | 否(需要对Proxy实例进行操作) |
| 3. 可劫持的操作 | get、set | get、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}