概念
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
Proxy vs Object.defineProperty()
vue使用Proxy 替代 Object.defineProperty()
下面对比下二者区别
1.Object.defineProperty只能劫持对象的属性,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历,而proxy是直接代理对象,不需要遍历操作
2.Object.defineProperty对新增属性需要手动进行observe,因为defineproperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象,再对新增属性再使用defineproperty进行劫持。而proxy可以直接监听对象属性的添加,vue2.0在给data中的数组或对象新增属性时,需要用vm.$set来保证新增属性的响应
3.Object.defineProperty 监听不到数组变化,也不能对 Map,Set 等数据结构做出监听
注意⚠️: Object.defineProperty IE8以下都不兼容 Proxy IE9以下都不兼容
vue3 Proxy劫持数据操作过程:
- 数据劫持
- 观察者
- 数据解析 {{}} 指令等
- diff算法 ->DOM
拦截器API
- get(target, propKey, receiver) :拦截对象属性的读取,比如
proxy.foo和proxy['foo']。 - set(target, propKey, value, receiver) :拦截对象属性的设置,比如
proxy.foo = v或proxy['foo'] = v,返回一个布尔值。 - has(target, propKey) :拦截
propKey in proxy的操作,返回一个布尔值。 - deleteProperty(target, propKey) :拦截
delete proxy[propKey]的操作,返回一个布尔值。 - ownKeys(target) :拦截
Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。 - getOwnPropertyDescriptor(target, propKey) :拦截
Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。 - defineProperty(target, propKey, propDesc) :拦截
Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。 - preventExtensions(target) :拦截
Object.preventExtensions(proxy),返回一个布尔值。 - getPrototypeOf(target) :拦截
Object.getPrototypeOf(proxy),返回一个对象。 - isExtensible(target) :拦截
Object.isExtensible(proxy),返回一个布尔值。 - setPrototypeOf(target, proto) :拦截
Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。 - construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)。
图是 从vue 3.0 Proxy的使用 搬的
get()
get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
let obj = {
name: "张三"
}
let newObj = new Proxy(obj, {
get(target, key, receiver) {
if (key in target) {
return target[key]
} else {
throw new ReferenceError("Prop name \"" + key + "\" does not exist.");
}
}
})
newObj.name; // 张三
newObj.age;
// 报错 Uncaught ReferenceError: Prop name "age" does not exist.
可以看到,这里获取对象数据被proxy代理拦截,在proxy内可以进行自定义修改返回值,这里返回的是 target[key] 真实值,下面的打印结果可以看到具体内容,获取你需要查的值,对象内部没有的值这里定义了报错,你也可以返回其他内容
get的三个参数:
set
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
let obj = {
name: "张三"
}
let newObj = new Proxy(obj, {
set(target, key, value, receiver) {
console.log("初始值", target, "\n修改的属性及修改后的值", key, value);
target[key] = value;
return true
}
})
newObj.name = "李四"; // 张三
newObj.age = 200;
console.log(newObj) // Proxy(Object) {name: '李四', age: 200}
运行结果:
has
has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
has()方法可以接受两个参数,分别是目标对象、需查询的属性名。
let obj = {
name: "张三"
}
let newObj = new Proxy(obj, {
has(target, key) {
return key in target
}
})
console.log("name" in newObj); // true
deleteProperty
deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
Reflect.deleteProperty() 也可以触发 deleteProperty方法
let obj = {
name: "张三",
age: '20'
}
let newObj = new Proxy(obj, {
deleteProperty(target, key) {
console.log("deleteProperty", target, key);
if (!target[key]) {
throw new ReferenceError("Prop name \"" + key + "\" does not exist.");
}
delete target[key]
return true
}
})
delete newObj.name
// 等价于
Reflect.deleteProperty(newObj, 'age')
// 不存在的属性直接抛出异常
Reflect.deleteProperty(newObj, 'sex') // 报错
newObj // Proxy(Object) {}
注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。
let obj = {
name: "张三",
age: '20'
}
Object.defineProperty(obj, 'name', {
enumerable: false,
writable: false,
configurable: false
});
let newObj = new Proxy(obj, {
deleteProperty(target, key) {
delete target[key]
return true
}
})
delete newObj.name // 报错
运行结果:
getOwnPropertyDescriptor
Object.defineProperty()用于设置对象属性
Object.defineProperty(obj, 'name', {
enumerable: false,
writable: false,
configurable: false
});
获取对象属性:
let obj = {
name: "张三",
age: '20'
}
let newObj = new Proxy(obj, {
getOwnPropertyDescriptor(target, key) {
return Object.getOwnPropertyDescriptor(target, key);
}
})
Object.getOwnPropertyDescriptor(newObj, 'name');
// {value: '张三', writable: true, enumerable: true, configurable: true}
ownKeys
ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。
Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()for...in循环
let obj = {
name: "张三",
age: '20'
}
let newObj = new Proxy(obj, {
ownKeys(target) {
console.log("ownKeys", target);
return ["1"]
}
})
Object.getOwnPropertyNames(newObj)
// ["1"]
通过Proxy实现简单双向绑定
实现输入的同时,下方同步展示输入内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" id="input" placeholder="请输入" />
<p id="text"></p>
<script>
const obj = {}
const input = document.getElementById("input");
const text = document.getElementById("text");
const newObj = new Proxy(obj, {
set(target, key, value) {
if (key === "text") {
input.value = value
text.innerHTML = value
}
return Reflect.set(target, key, value)
},
get(target, value) {
return Reflect.get(target, key)
}
})
input.addEventListener('keyup', function (e) {
newObj.text = e.target.value
})
</script>
</body>
</html>
运行结果: