手写Proxy
defineProperty的用法
Object.Property主要实现的是对对象属性描述符的管理,属性描述符一共分元属性和存取器两种。
其中元属性为:
- value 控制值
- writable 控制是否可写
- enumerable 控制是否可遍历
- configurable 控制是否能改变属性描述符
存取器为:
- getter
- setter 如果设置了存取器,那么元属性value就无效。
一般存取器是这样用的
const obj={
money:1000,
get m(){return this.money},
set m(newValue){this.money=newValue}
}
obj.m //1000
obj.m = 2000
onj.m //2000
obj.money //1000
obj.money=3000
obj.m //3000
了解了基本原理,下面就通过Object.defineProperty来返回一个代理。
目标:
- 对该代理的修改操作,都将改变目标对象的属性
- 该代理有数据拦截功能,现在我也不知道拦截了能干啥,就打印一段话吧
代码
const target={
name:'qiuyanxi',
message:{
id:123,
age:28,
}
}
function myProxy(target){
const obj={}
Object.keys(target).forEach((key,)=>{
Object.defineProperty(obj,key,{
get(){
console.log(`get了${key}`)
return target[key]
},
set(newValue){
console.log(`set了${key}`)
target[key]=newValue
}
})
})
return obj
}
const p=myProxy(target)
console.log(p.name)
p.name='123456'
console.log(target.name)
以下是打印台信息
"get了name"
"qiuyanxi"
"set了name"
"123456"
可以看到我们设置了p.name后,target.name也跟着改变了,同时也出现拦截下来的信息。
不过上面的代码并没有把p.message也改成拦截模式,所以呢我们还需要使用递归来把数据类型为object的属性也设置成拦截器模式
const target = {
name: "qiuyanxi",
message: {
id: 123,
age: 28,
mes: { name: "jack" } //这里又加了一层嵌套
}
};
function myProxy(target) {
if (typeof target === "object") {
const obj = {};
for (let k in target) {
if (typeof target[k] !== "object") {
Object.defineProperty(obj, k, {
get() {
return target[k];
},
set(newValue) {
target[k] = newValue;
}
})
} else {
obj[k] = myProxy(target[k])
}
}
return obj;
}
}
const p = myProxy(target);
来看看打印台看代理的属性
每一层都已经做上了拦截器
再来看看原来的target属性有没有设置拦截器
一点都没有影响,很好。
优化代码
const target = {
name: "qiuyanxi",
message: {
id: 123,
age: 28,
mes: { name: "jack" }, //这里又加了一层嵌套
},
};
function myProxy(target) {
const obj = {};
for (let k in target) {
if (typeof target[k] !== "object") {
define(obj, k, target);
} else {
obj[k] = myProxy(target[k]);
}
}
return obj;
}
function define(obj, key, target) {
Object.defineProperty(obj, key, {
get() {
return target[key];
},
set(newValue) {
target[key] = newValue;
},
});
}
const p = myProxy(target);
测试用例
p.name='我改了代理的name'
p.message.id='我改了代理的message的id'
p.message.mes.name='我改了代理的message的mes的name'
console.assert(p.name===target.name,'报错说明拦截器没用')
console.assert(p.message.id===target.message.id,'报错说明拦截器没用')
console.assert(p.message.mes.name===target.message.mes.name,'报错说明拦截器没用')
target.name='我改了目标对象的name'
target.message.id='我改了目标对象的message的id'
target.message.mes.name='我改了目标的message的mes的name'
console.assert(p.name===target.name,'报错说明拦截器没用')
console.assert(p.message.id===target.message.id,'报错说明拦截器没用')
console.assert(p.message.mes.name===target.message.mes.name,'报错说明拦截器没用')
缺陷
虽然上面已经实现了一个代理,但是有个缺陷,那就是没有办法控制数组,不信我们用一个数组试一试
const array=[1,2,3]
const p = myProxy(array);
console.log(p)
p[0]=666 //修改代理的第0个值
console.log(array[0]) //666
p.push(888) //TypeError: p.push is not a function
数组的方法虽然能保持已有数组的同步修改,但是缺点是不能通过代理push(以及其他数组方法),难怪vue2用了自己做的数组方法
Proxy构造函数
下面学习一下新增的Proxy构造函数
基本用法
const p = new Proxy(target, handler)
-
target 目标对象
-
handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
const array=[1,2,3]
const p=new Proxy(array,{})
p.push(99999)
console.log(array) // [1, 2, 3, 99999]
上面的语法p是生成的proxy对象,可以直接对数组array进行代理,而且我push一个数字之后,array就马上会变化。
使用proxy拦截示例
const obj={}
const obj2=new Proxy(obj,{
set(obj,props,value){
if(props==='name'){
obj.name=value
}else{
throw new Error('不能设置其他属性')
}
}
})
obj2.name=123
obj2.aaa='9999' // Error: 不能设置其他属性
console.log(obj) //{name:123}
代理对象示例
const target = {
name: "qiuyanxi",
message: {
id: 123,
age: 28,
mes: { name: "jack" } //这里又加了一层嵌套
}
};
const p = new Proxy(target,{});//没错,就一行。。。
可以用我上面的测试用例测一下,proxy实现了完全掌控,连递归都不放过,真的香。 不过上面就是纯代理,生成的p是一个proxy对象,它的任何修改都会影响到原数据。
增加数据拦截
const target = {
name: "qiuyanxi",
message: {
id: 123,
age: 28,
mes: { name: "jack" } //这里又加了一层嵌套
}
}
function myProxy(obj){
for(let k in obj){
if(typeof obj[k]==='object'){
obj[k]=myProxy(obj[k])
}
}
return new Proxy(obj,{
get(o,p){
if(p in o){
console.log('get'+p)
return o[p]
}
},
set(o,p,v){
console.log('set'+p)
o[p]=v
}
})
}