最近在温习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=20newTarget.name = 'oyq'; //Set name=oyqReflect.set(newTarget, 'name', 'oyq'); //Set name=oyq
从这能够看出,Reflect和trap体现进去的行为是雷同的。所以当你为如何去触发trap而懊恼的时候,兴许这个Reflect能够帮到你。
两个问题
大抵学习完proxy的内容后,再来尝试解答下页头提到的两个个问题。
vue3实现响应式为什么要应用proxy替换Object.defineProperty?优缺点?
长处
- 性能更好,Object.defineProperty只能劫持对象的属性,所以如果有嵌套对象,初始化时须要遍历data中的每个属性,在vue3中,proxy能够代理对象,不须要像vue2那样对属性进行遍历的操作
//vue2function 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操作 })}//vue3let newTarget = new Proxy(obj, { // set,get操作})
- 主动代理新增属性,数组,Object.defineProperty的实现是对属性进行劫持,所以当新增属性时,须要从新遍历,对新增的从新进行劫持。所以须要vue2对新增的属性,以及数组进行 $set 能力保障属性是响应式的,这个过程是手动的。
let obj = { a: 10, name: 'oyc'}//vue2this.$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=20myObj.b = 30; //新增属性 Render b=30myObj.list = [1, 2, 5, 6]; //批改数组 //Render list=1,2,5,6myObj.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 numberproxyObj.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