最近在温习 Vue,不可避免的会接触到 vue3,所以也不可避免的会思考这些问题
- vue3 实现响应式为什么要应用 proxy 替换 Object.defineProperty?Proxy 比照 Object.defineProperty 有啥优缺点?
- 怎么通过 Proxy 实现响应式?
本文会答复这两个问题,通过这些问题探讨 Proxy,以及 Proxy 在日常开发中的利用场景。
意识 Proxy
Proxy 意思翻译过去就是 代理,外界对指标对象的拜访都会被 Proxy 拦挡,从而能够实现基本操作的拦挡和自定义。
用法
let proxy = new Proxy(target,handler)
- target: 所要拦挡的指标对象
- handler: handler 是一个蕴含你要拦挡和解决的对象,当对象被代理时,handler 通过捕获器 (trap) 实现对各种行为的拦挡
目前 proxy 反对 13 种行为的拦挡
handler 办法 | 何时触发 |
---|---|
get | 读取属性 |
set | 写入属性 |
has | in 操作符 |
deleteProperty | delete 操作符 |
apply | 函数调用 |
construct | new 操作符 |
getPrototypeOf | Object.getPrototypeOf |
setPrototypeOf | Object.setPrototypeOf |
isExtensible | Object.isExtensible |
preventExtensions | Object.preventExtensions |
defineProperty | Object.defineProperty, Object.defineProperties |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor, for…in, Object.keys/values/entries |
ownKeys | Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for…in, Object.keys/values/entries |
Reflect
reflect 翻译过去是映射的意思,在 MDN 上是这样定义的
Reflect 是一个内置的对象,它提供拦挡 JavaScript 操作的办法。
每个可用的代理捕获器 (trap) 都有一个对应的同名 Reflect 函数,并能产生雷同的行为。
let obj = {
a: 10,
name: 'oyc'
}
let newTarget = new Proxy(obj, {set(target, key, val) {console.log(`Set ${key}=${val} `);
}
})
// newTarget.a = 20; //Set a=20
// Reflect.set(newTarget, 'a', 20); //Set a=20
newTarget.name = 'oyq'; //Set name=oyq
Reflect.set(newTarget, 'name', 'oyq'); //Set name=oyq
从这能够看出,Reflect 和 trap 体现进去的行为是雷同的。所以当你为如何去触发 trap 而懊恼的时候,兴许这个 Reflect 能够帮到你。
两个问题
大抵学习完 proxy 的内容后,再来尝试解答下页头提到的两个个问题。
vue3 实现响应式为什么要应用 proxy 替换 Object.defineProperty?优缺点?
长处
- 性能更好,Object.defineProperty 只能劫持对象的属性,所以如果有嵌套对象,初始化时须要遍历 data 中的每个属性, 在 vue3 中,proxy 能够代理对象,不须要像 vue2 那样对属性进行遍历的操作
//vue2
function reactive(obj) {
// 遍历对象
for (const item in obj) {if (obj[item] && typeof obj[item] == 'object') {
// 递归,又从新遍历
reactive(obj[item])
} else {defineReactive(obj, item, obj[item])
}
}
}
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {//set,get 操作})
}
//vue3
let newTarget = new Proxy(obj, {// set,get 操作})
- 主动代理新增属性, 数组,Object.defineProperty 的实现是对属性进行劫持,所以当新增属性时,须要从新遍历,对新增的从新进行劫持。所以须要 vue2 对新增的属性,以及数组进行 $set 能力保障属性是响应式的,这个过程是手动的。
let obj = {
a: 10,
name: 'oyc'
}
//vue2
this.$set(this.obj, 'age', 18); // 每次新增都须要进行这个操作
//vue3
// 主动代理
let newTarget = new Proxy(obj, {get(target, key) {return Reflect.get(target, key);
},
set(target, key, val) {return Reflect.set(target, key, val);
}
})
- Proxy 反对 13 中拦挡操作,Object.defineProperty 无法比拟
- Proxy 是新规范,后续也会优先优化,Object.defineProperty 的 setter,getter 后续应该优化的优先级较低
毛病
不言而喻的,Proxy 的兼容性相较于 Object.defineProperty,是较低的,不反对 IE 浏览器。不过以目前的市场份额来看,IE 浏览器的市场份额也不多,目前微软也将 ie 换成了 chrome 内核的 edge,所以激进点的我的项目是齐全能够应用 proxy 的。
怎么通过 Proxy 实现响应式?
let obj1 = {
a: 10,
name: 'John',
list: [1, 2, 3],
obj2: {
obj3: 'oo',
obj4: {name: 'oyc'}
}
}
// 判断是否是对象
const isObj = (obj) => typeof obj === 'object' && obj !== null;
const render = (key, val) => {console.log(`Render ${key}=${val}`);
}
function reactive(obj) {if (!isObj(obj)) {return obj;}
const handler = {get(target, key) {
// 对嵌套对象遍历
if (isObj(target[key])) {
// 递归
return reactive(target[key]);
}
return Reflect.get(target, key);
},
set(target, key, val) {
// 渲染
render(key, val);
return Reflect.set(target, key, val);
}
}
const targetProxyObj = new Proxy(obj, handler);
return targetProxyObj
}
let myObj = reactive(obj1);
myObj.a = 20; // Render a=20
myObj.b = 30; // 新增属性 Render b=30
myObj.list = [1, 2, 5, 6]; // 批改数组 //Render list=1,2,5,6
myObj.obj2.obj4.name = 'oyq'; // 批改嵌套对象 //Render name=oyq
Proxy 利用场景
- 写入默认值, 日常开发常常碰到 ReferenceError: xxx is not defined 这种谬误,这里咱们能够代理,当属性不存在时,不报错,而设置一个默认值
let obj = {name: 'oyq'}
let proxyObj = new Proxy(obj, {get(target, key) {if (Reflect.has(target, key)) {return target[key];
} else {return 'OYC';}
},
})
console.log(proxyObj.age);//OYC
- 用 Proxy 来包装 fetch, 让 fetch 更易用
let handlers = {get (target, property) {if (!target.init) {
// 初始化对象
['GET', 'POST'].forEach(method => {target[method] = (url, params = {}) => {
return fetch(url, {
headers: {'content-type': 'application/json'},
mode: 'cors',
credentials: 'same-origin',
method,
...params
}).then(response => response.json())
}
})
}
return target[property]
}
}
let API = new Proxy({}, handlers)
await API.GET('XXX')
await API.POST('XXX', {body: JSON.stringify({name: 1})
})
- 测验表单
let formData = {
name: '',
age: ''
}
let proxyObj = new Proxy(formData, {set(target, key, val) {if (key === 'age' && typeof val !== 'number') {console.log('age must be number');
}
if (key === 'name' && typeof val !== 'string') {console.log('name must be string');
}
}
})
proxyObj.age = 'oyc'; //age must be number
proxyObj.name = 18; //name must be string
- 负索引数组
let arr = [1, 2, 3, 4, 5]
let proxyArray = new Proxy(arr, {get(target, key) {const index = key < 0 ? target.length + Number(key) : key;
return Reflect.get(target, index)
}
})
console.log(proxyArray[-1]); //5