众所周知,尤大在Vue3.0的响应式中做了改变,从2.0的Object.defineProperty到现在的Proxy。 为什么会从Object.defineProperty更换到Proxy? 答案:肯定是Proxy更加好使!那有哪些地方比Object.defineProperty更加优秀那?接下来我们往下看~
以下便是我的学习和理解
如有不足之处还请指正!谢谢您的阅读~
Vue2.0中defineProperty是如何去做到进行响应式的:
//源数据
let person = {
name: '韩梅梅',
age: 22
}
// 模拟Vue2中实现响应式
let p = {}
Object.defineProperty(p,'name',{
//配置是否可以删除 对象属性
configurable:true,
//读取调用
get(){
return person.name
},
//修改调用
set(value){
console.log('修改了name!')
person.name = value
}
})
复制代码
我们再来看一下控制台上面的输出。
可以看到:
当我们在修改name的时候,我们触发了set()。
当我们读取属性的时候,其实走的是get()这里没有打印出来。
在defineProperty中当我们想去使用delete 去删除某一个属性时,我们需要进行配置一下 configurable属性,为true时可删除,否则不可以,删除。这个状态只可定义一次,而且之后不可改变。
这个就是Vue2.0的响应式核心:
对象类型:Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
数组类型:通过重写更新数组的一系列方法来实现拦截,(对数组的变更方法进行了包裹)
Object.defineProperty(data, 'count',{
get() {},
set() {}
})
复制代码
存在的问题:
- 新增属性、删除属性、界面不会更新。
- 直接通过下标修改数组,界面不会自动更新
vue中,实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。这里需要注意,递归的时候由于属性的值可能也是一个对象,在递归处理对象属性值的时候 ,递归循环引用对象很容易出现递归爆栈问题,在vue中已经通过定义obj对象记录已经被设置过setter和getter方法的对象,避免了此问题,但如果需要扩展对象,必须手动给新属性设置setter和getter方法,这就是为什么不在data中预先声明好的属性无法进行双向绑定,需要通过this.$set()来设置。
对于Object.defineProperty可以监测数组下标变化吗?
这个答案是肯定的。
尤大通过原型继承得到一个新的原型对象,在此基础上,劫持了7种常用的数组操作进行了重写,分别是push() 、pop() 、shift()、 unshift() 、splice() 、sort()、 reverse(),Vue.set()对于数组的处理其实就是调用了splice方法...
Proxy
Proxy的出现,给vue响应式带来了极大的便利,比如可以直接劫持数组、对象的改变,可以直接添加对象属性,但是兼容性可能会有些问题...
废话不多说,我们直接来看Vue3.0是如何通过 Proxy进行实现响应式的!
//源数据
let person = {
name: '韩梅梅',
age: 22
}
//模拟Vue3中实现原理
//person 源数据
//Proxy代理对象
//Reflectduix1
const p = new Proxy(person,{
get(target,prpoName){
console.log('读取P身上得一个属性--->',prpoName)
//真正的修改person
return Reflect.get(target,prpoName)
},
//有人读取P的某个属性、或给P追加某一个属性是调用
set(target,prpoName,value){
console.log(`有人修改了P身上${prpoName}属性,我要去更新界面`)
//真正的修改person
Reflect.set(target,prpoName,value)
},
deleteProperty(target, prpoName) {
console.log(`有人修改了P身上${prpoName}属性,我要去更新界面`)
console.log(target,prpoName)
//真正的修改person
return Reflect.deleteProperty(target, prpoName)
}
})
复制代码
我们可以很清晰的看到, 这里面使用了三个函数:set()、get()、 deleteProperty() 而在我们的添加元素和修改元素我们都是调用了set()。
在Vue3.0的响应式中,不光是用到了Proxy,还用到了Reflect也就是反射对象。
我个人感觉是使用Reflect有一个很大的好处,最大的作用就是让你的try catch 的少一些(这里指的是你直接去封装框架),使用了Reflect就不在需要进行try catch了,但是感觉也有缺点,就是没有了那种即使的指出报错。
let obj = {a: 1, b: 2}
const d1 = Reflect.defineProperty(obj, 'c', {
get() {
return 3
}
})
const d2 = Reflect.defineProperty(obj, 'c', {
get() {
return 4
}
})
if (d2) {
console.log('操作成功')
} else {
console.log('操作失败')
console.log(obj)
}
// 测试是否继续往下执行
console.log('well')
复制代码
我们在来聊一下defineProperty和Proxy的一些不同点:
1、proxy 能观察的类型比defineProperty更丰富
2、proxy 不兼容ie,也没有polyfill,defineProperty 能支持到ie9
3、Object.defineProperty 是劫持对象的属性,新增元素需要再次defineProperty.而proxy劫持的是整个对象,不需要做特殊处理
4、使用defineProperty时,我们修改原来的obj对象就可以触发拦截,而使用proxy,就必须修改代理对象,即proxy的实例
5、对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到
6、数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
当然Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。想要进一步了解的可以移步去看阮老师的讲解的Proxy、Reflect
-
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)。
放一张 浏览器支持Proxy的图
可以看到,Proxy对于IE浏览器简直一点爱都没有...