-
vue2响应式设计
vue2利用Object.defineProperty来劫持data数据的getter和setter操作,遇到数组和对象必须循环遍历所有的域值才能劫持每一个属性 。
Object.keys(data).forEach((prop) => { const dep = new Dep(); Object.defineProperty(data, prop, { get () { dep.depend(); return Reflect.get(data, prop); }, set (newVal) { Reflect.set(data, prop, newVal); dep.notify(); } }); });
缺点:1、 无法检测到对象属性的新增或删除 ,需要使用 set 等其他方法。
2、 不能监听数组的变化 ,局限于数组的push/pop/shift/unshift/splice/sort/reverse
七个方法
而更快的es6中的原生proxy就能解决这些问题,为什么vue3才使用 Proxy ,原因还是浏览器兼容所限。至今IE仍不支持Proxy,所以vue3还是为原始浏览器保留了Object.defineProperty
的实现。
-
什么是proxy
MDN :Proxy`对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)
可以理解为在目标对象之前做一层拦截,外部所有的访问都必须通过这层拦截,通过这层拦截可以做很多事情,比如对数据进行过滤、修改或者收集信息之类。
let obj = { a : 1 } let proxyObj = new Proxy(obj,{ get : function (target,prop) { return prop in target ? target[prop] : 0 }, set : function (target,prop,value) { target[prop] = 888; } }) console.log(proxyObj.a); // 1 console.log(proxyObj.b); // 0 proxyObj.a = 666; console.log(proxyObj.a) // 888
Proxy构造函数的第一个参数是原始数据data;第二个参数是一个叫handler的处理器对象。Handler是一系列的代理方法集合,它的作用是拦截所有发生在data数据上的操作。这里的get()和set()是最常用的两个方法,分别代理访问和赋值两个操作。
const list = [1, 2]; const observer = new Proxy(list, { set: function(obj, prop, value, receiver) { console.log(`prop: ${prop} is changed!`); return Reflect.set(...arguments); }, }); observer.push(3); observer[3] = 4;
Proxy不需要各种hack技术就可以无压力监听数组变化;甚至有比hack更强大的功能——自动检测length。除此之外,Proxy还有多达13种拦截方式,包括construct
、deleteProperty
、apply
等等操作;而且性能也远优于Object.defineProperty
。
let handler = { get(target, key){ if (target[key] === 'object' && target[key]!== null) { // 嵌套子对象也需要进行数据代理 return new Proxy(target[key], hanlder) } collectDeps() // 收集依赖 return Reflect.get(target, key) }, set(target, key, value) { if (key === 'length') return true notifyRender() // 通知订阅者更新 return Reflect.set(target, key, value); } } let proxy = new Proxy(data, handler); proxy.age = 18 // 支持新增属性 let proxy1 = new Proxy({arr: []}, handler); proxy1.arr[0] = 'proxy' // 支持数组内容变化
Proxy代理目标对象,每个拦截方式与ES6提供的另一个apiReflect的13种静态方法一一对应。二者一般是配合使用的,在修改proxy代理对象时,一般也需要同步到代理的目标对象上,这个同步就是用Reflect对应方法来完成的。例如上面的Reflect.set(target, key, value)同步目标对象属性的修改。
-
Proxy的13种拦截方式
trap | 描述 |
---|---|
handler.get | 获取对象的属性时拦截 |
handler.set | 设置对象的属性时拦截 |
handler.has | 拦截propName in proxy的操作,返回boolean |
handler.apply | 拦截proxy实例作为函数调用的操作,proxy(args)、proxy.call(...)、proxy.apply(..) |
handler.construct | 拦截proxy作为构造函数调用的操作 |
handler.ownKeys | 拦截获取proxy实例属性的操作,包括Object.getOwnPropertyNames、Object.getOwnPropertySymbols、Object.keys、for...in |
handler.deleteProperty | 拦截delete proxy[propName]操作 |
handler.defineProperty | 拦截Objecet.defineProperty |
handler.isExtensible | 拦截Object.isExtensible操作 |
handler.preventExtensions | 拦截Object.preventExtensions操作 |
handler.getPrototypeOf | 拦截Object.getPrototypeOf操作 |
handler.setPrototypeOf | 拦截Object.setPrototypeOf操作 |
handler.getOwnPropertyDescriptor | 拦截Object.getOwnPropertyDescriptor操作 |
-
Proxy的应用场景
1、 Vue3的数据响应
2、 获取属性对应的值,无该属性或者属性为空返回默认值
3、 实现数组负数索引的访问
4、缓存
5、隐藏属性
6、只读视图
-
Proxy的缺点
大部分浏览器支持Proxy特性,但是一些浏览器或者其低版本不支持Proxy,其中IE、QQ浏览器、百度浏览器等完全不支持,因此Proxy有兼容性问题。也不能像ES6其他特性那样有对应的polyfill解决方案。