- 什么是Proxy? 它的作用是
- get(target, propKey, receiver)
- set(target, propKey, value, receiver)
- has(target, propKey)
- construct(target, args, newTarget)
- apply(target, object, args)
- 使用Proxy实现简单的vue双向绑定
- 什么是Proxy? 它的作用是
Proxy可以理解成,在目标对象之前架设一层 “拦截”,当外界对该对象访问的时候,都必须经过这层拦截,而Proxy就充当了这种机制,类似于代理的含义,它可以对外界访问对象之前进行过滤和改写该对象。
Object.defineProperty() 和 Proxy 对象,都可以用来对数据的劫持操作。何为数据劫持呢?就是在我们访问或者修改某个对象的某个属性的时候,通过一段代码进行拦截行为,然后进行额外的操作,然后返回结果。那么vue中双向数据绑定就是一个典型的应用。
Vue2.x 是使用 Object.defindProperty(),来进行对对象的监听的。
Vue3.x 版本之后就改用Proxy进行实现的。
vue2.xx中使用 Object.defineProperty()方法对该对象通过 递归+遍历的方式来实现对数据的监控的,当使用数组的方法或改变数组的下标是不能重新触发 Object.defineProperty中的set()方法的,因此就做不到实时响应了。所以使用 Object.defineProperty 存在如下缺点:
- 监听数组的方法不能触发Object.defineProperty方法中的set操作(如果要监听的到话,需要重新编写数组的方法)。
- 必须遍历每个对象的每个属性,如果对象嵌套很深的话,需要使用递归调用。
因此vue3.xx中之后就改用Proxy来更好的解决如上面的问题。在学习使用Proxy实现数据双向绑定之前,我们还是一步步来,先学习了Proxy基本知识点。
Proxy基本语法
const obj = new Proxy(target, handler);
target: 被代理对象。
handler: 是一个对象,声明了代理target的一些操作。
obj: 是被代理完成之后返回的对象。
但是当外界每次对obj进行操作时,就会执行handler对象上的一些方法。handler中常用的对象方法如下:
- get(target, propKey, receiver)
const target = {
name: "proxy",
};
const handler = {
get: function (target, key) {
console.log(`${key} 被读取`);
return target[key];
},
set: function (target, key, value) {
console.log(`${key} 被设置为 ${value}`);
target[key] = value;
},
has: function (target, key) {
if (Reflect.has(target, key)) {
return true;
} else {
return false;
}
},
};
const testObj = new Proxy(target, handler);
// target: 被代理对象。
// handler: 是一个对象,声明了代理target的一些操作。
// testObj: 是被代理完成之后返回的对象。
console.log("testObj", testObj);
//获取testObj中name属性值
//会自动执行 get函数后 打印信息:name 被读取 及输出名字 kongzhi
console.log("testObj.name", testObj.name);
复制代码
- set(target, propKey, value, receiver)
该方法是用来拦截某个属性的赋值操作,它可以接受四个参数,参数解析分别如下:
target: 目标对象。
propKey: 目标对象的属性名
value: 属性值
receiver(可选): 一般情况下是Proxy实列
//改变target中的name属性值
testObj.name = "你好";
console.log("target.name", target.name); // 输出 你好
// target是被代理的对象,handler是代理target的, 那么handler上面有set和get方法,
// 当每次打印target中的name属性值的时候会自动执行handler中get函数方法,当每次设置 target.name 属性值的时候,会自动调用 handler中的set方法,因此target对象对应的属性值会发生改变,同时改变后的 testObj对象也会发生改变。
// 同理改变返回后 testObj对象中的属性也会改变原对象target的属性的,因为对象是引用类型的,是同一个引用的。
// 定义了一个对象其实是在栈内存中存储了一个指针,这个指针指向堆内存中该对象的存储地址。复制给另一个对象的过程其实是把该对象的地址复制给了另一个对象变量,两个指针都指向同一个对象,所以若其中一个修改了,则另一个也会改变。
writable: false,设置目标对象自身的某个属性不可写,那么set方法将不起作用。
复制代码
- has(target, propKey)
该方法是判断某个目标对象是否有该属性名。接收二个参数,分别为目标对象和属性名。返回的是一个布尔型。
const obj = {
'name': 'kongzhi'
};
const handler = {
has: function(target, key) {
if (Reflect.has(target, key)) {
return true;
} else {
return false;
}
}
};
const proxy = new Proxy(obj, handler);
console.log(Reflect.has(obj, 'name')); // true
console.log(Reflect.has(obj, 'age')); // false
复制代码
- construct(target, args):
该方法是用来拦截new命令的,它接收三个参数,分别为 目标对象,构造函数的参数对象及创造实列的对象。
第三个参数是可选的。它的作用是拦截对象属性。
function A(name) {
this.name = name;
}
const handler = {
construct: function(target, args, newTarget) {
/*
输出: function A(name) {
this.name = name;
}
*/
console.log(target);
// 输出: ['kongzhi', {age: 30}]
console.log(args);
return args
}
};
const Test = new Proxy(A, handler);
const obj = new Test('kongzhi', {age: 30});
console.log(obj); // 输出: ['kongzhi', {age: 30}]
复制代码
- apply(target, object, args)
该方法是拦截函数的调用的。该方法接收三个参数,分别是目标对象。目标对象上下文this对象 和 目标对象的数组;它和 Reflect.apply参数是一样的
var target = function () {
return "I am the target";
};
var handler = {
apply: function () {
return { msg: '"I am the proxy"' };
},
};
var p = new Proxy(target, handler);
p();
console.log(p);
console.log(p());
//变量p是 Proxy 的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。
复制代码
使用proxy 实现vue数据双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<h3 id="value"></h3>
<input type="text" id="input" />
</div>
</body>
<script>
const demo = document.getElementById("value");
const input = document.getElementById("input");
const data = {
text: "hello",
msg: "vue",
};
const handler = {
set: function (target, prop, value) {
console.log(target, prop, value);
if (prop === "text") {
target[prop] = value;
demo.innerHTML = value;
return true;
} else {
return false;
}
},
};
const myTest = new Proxy(data, handler);
input.addEventListener(
"input",
(e) => {
myTest.text = e.target.value;
},
false
);
</script>
</html>
复制代码