前言
vue3出来也有段时间了,无疑被说的最多应该就是vue的数据拦截用proxy重写了(之前用的是Object.defineProperty)和 Composition API 了。本篇文,来体验一下proxy的拦截能力到底有多优秀。
Object.defineProperty数据拦截和proxy数据拦截的对比
这里通过vue1.x,vue2.x
时的数据拦截来说一下Object.defineProperty
。
提前说明,这里的数据拦截,不包含vue依赖收集,视图更新,不然又给可爱的掘友骂死。
Object.defineProperty
先来用Object.defineProperty
实现一下对象的拦截。
let data = {
m:234,
n:[1,34,4,5676],
h:{
c:34
}
}
function observer(data){
if(typeof data === 'object'){
Object.keys(data).forEach(key=>{
defineReactive(data,key,data[key])
})
}
}
function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
get(){
console.log('get')
return val
},
set(newVal){
console.log('set')
if(newVal !== val ) val = newVal
}
})
}
上面通过遍历data
的数据,进行了一次简单的拦截;看似没有问题,但如果我们改变data.h.c
是不会触发set钩子的
,为什么?因为这里还没有实现递归
,所以只拦截了最表面的一层
,里面的则没有被拦截。
递归拦截对象
function defineReactive(obj,key,val){
observer(val)
Object.defineProperty(obj,key,{
get(){
console.log('get')
return val
},
set(newVal){
console.log('set')
if(newVal !== val ) val = newVal
}
})
}
递归拦截,只要在defineReactive函数
再调一次observer函数
把要拦截的值传给它就行。这样,就实现了对象的多层拦截。但是呢,现在是拦截不到数组的,当我们调用push,pop等方法
它是不会触发set钩子的
,为什么?因为Object.defineProperty
压根就不支持数组的拦截。既然它不支持,那么我们只能拦截它的这些('push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse')
改变自身数据的方法了。
Object.defineProperty数组的拦截
function arrayMethods(){
const arrProto = Array.prototype
const arrayMethods = Object.create(arrProto)
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
methods.forEach(function (method) {
const original = arrProto[method]
Object.defineProperty(arrayMethods, method, {
value: function v(...args) {
console.log('set arrayMethods')
return original.apply(this, args)
}
})
})
return arrayMethods
}
以上就是对这些数组的原型方法进行了一个拦截,然后把它覆盖要拦截的数组的原型就行,下面简单修改一下observer
function observer(data){
if(typeof data === 'object'){
if(Array.isArray(data)){
data.__proto__ = arrayMethods()
}else{
Object.keys(data).forEach(key=>{
defineReactive(data,key,data[key])
})
}
}
}
在vue
中,还会判断该key
有没有__proto__
,如果没有就直接把这些方法放到这个key的自身上,如果有就直接覆盖这个__proto__
。
完整代码
function arrayMethods(){
const arrProto = Array.prototype
const arrayMethods = Object.create(arrProto)
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
methods.forEach(function (method) {
const original = arrProto[method]
Object.defineProperty(arrayMethods, method, {
value: function v(...args) {
console.log('set arrayMethods')
return original.apply(this, args)
}
})
})
return arrayMethods
}
function observer(data){
if(typeof data === 'object'){
if(Array.isArray(data)){
data.__proto__ = arrayMethods()
}else{
Object.keys(data).forEach(key=>{
defineReactive(data,key,data[key])
})
}
}
}
function defineReactive(obj,key,val){
observer(val)
Object.defineProperty(obj,key,{
get(){
console.log('get')
return val
},
set(newVal){
console.log('set')
if(newVal !== val ) val = newVal
}
})
}
observer(data)
以上,就完成了对对象和数组
的拦截(说明:vue2.x中的实现会比这里复杂,但大概思路和大概实现是这样)
,看似辛苦点,换来一个完美的结果挺不错的。但真的是你想的那样吗?试一下调用data.n[1] = xxx
,它是不会触发set钩子的
,这也是在proxy
出现之前,无能无力的,所以在vue中提供了$set,$delete
API。
霸气的proxy登场
这里就不介绍proxy了,就当你对它有了解过了。直接上代码
let data = {
m:234,
n:[1,34,4,5676],
h:{
c:34
}
}
function defineReactive(obj){
Object.keys(obj).forEach((key) => {
if(typeof obj[key] === 'object'){
obj[key] = defineReactive(obj[key])
}
})
return new Proxy(obj,{
get(target,key){
console.log('get')
return target[key]
},
set(target,key,val){
console.log('set')
return target[key] = val
}
})
}
data = defineReactive(data)
就这么一点代码就实现了对对象和数组
的拦截(说明:这里不是vue3的实现方法,vue3怎么实现的,我还不知道,还没看过它的源码,有兴趣自己去看一下,然后顺便告诉我一下怎么实现的,哈哈哈)
,Object.defineProperty
实现不了的,它能实现;Object.defineProperty
实现的了,它也能实现。无论你调push,pop等方法
它能拦截,你调data.n[1] = xxx
也能拦截,简直不要太爽,这个两个版本的实现,给我个人的感觉就是一个韩红版(肉多腿短),一个迪丽热巴版(苗条腿长),哈哈哈,自己品。
最后
这里只是通过对对象和数组
的拦截,来体验了一下proxy的威力;proxy能做的远远不止这样。
-
了解proxy更多特性,可以看看我之前整理比较基础《Proxy入门》
-
了解proxy更多用法,可以看看,前端小智的《Proxy 的巧用》
如果你想了解更多proxy的特性,和更多的用例可以,看看以上两篇文章(自愿看哈,我不想给骂,说什么广告,瞎jb推荐之类的,🐶保命)。