详解实现vue的数据响应式原理
这篇文章主要是给不了解或者没接触过vue响应式源码的小伙伴们看的,其主要目的在于能对vue的响应式原理有个基本的认识和了解,如果在面试中被问到此类问题,能够知道面试官想让你回答的是什么?「PS:文中如有不对的地方,欢迎小伙伴们指正」
响应式的理解
响应式顾名思义就是数据变化,会引起视图的更新。这篇文章主要分析vue2.0中对象和数组响应式原理的实现,依赖收集和视图更新我们留在下一篇文章分析。
在vue中,我们所说的响应式数据,一般指的是数组类型和对象类型的数据。vue内部通过Object.defineProperty方法对对象的属性进行劫持,数组则是通过重写数组的方法实现的。下面我们就简单实现一下。
首先我们定义一个需要被拦截的数据
constvm=newVue({
data(){
return{
count:0,
person:{name:'xxx'},
arr:[1,2,3]
}
}
})
letarrayMethods
functionVue(options){//这里只考虑对data数据的操作
letdata=options.data
if(data){
data=this._data=typeofdata==='function'?data.call(this):data
}
observer(data)
}
functionobserver(data){
if(typeofdata!=='object'||data===null){
returndata
}
if(data.__ob__){//存在__ob__属性,说明已经被拦截过了
returndata
}
newObserver(data)
}
这里的arrayMethods、Observer、__ob__的实现和作用请继续往下看
实现Observer类
classObserver{
constructor(data){
Object.defineProperty(data,'__ob__',{//在data上定义__ob__属性,在数组劫持里需要用到
enumerable:false,//不可枚举
configurable:false,//不可配置
value:this//值是Observer实例
})
if(Array.isArray(data)){//对数组进行拦截
data.__proto__=arrayMethods//原型继承
this.observerArray(data)
}else{//对象进行拦截
this.walk(data)
}
}
walk(data){
constkeys=Object.keys(data)
for(leti=0;iobserver(value))
}
}
对象的拦截
对象的劫持需要注意的几点:
- 遍历对象,如果值还是对象类型,需要重新调用observer观测方法
- 如果设置的新值是对象类型,也需要被拦截
//处理对象的拦截
functiondefineReactive(data,key,value){
observer(value)//如果value值仍是对象类型,需要递归劫持
Object.defineProperty(data,key,{
get(){
returnvalue
},
set(newValue){
if(newValue===value)return
value=newValue
observer(newValue)//如果设置newValue值也是对象类型,需要被劫持
}
})
}
数组的劫持
数组的劫持需要注意的几点:
- 数组是使用函数劫持(切片编程)的思想,对数据进行拦截的
- 数组里新增加的值,如果是对象类型,也需要被重新拦截
constoldArrayPrototype=Array.prototype
arrayMethods=Object.create(oldArrayPrototype)
constmethods=['push','pop','shift','unshift','splice','sort','reverse']//能够改变原数组的方法
methods.forEach(method=>{
arrayMethods[methods]=function(...args){
constresult=oldArrayPrototype[methods].call(this,...args)
constob=this.__ob__//this就是调用改方法的数组
letinserted;//数组新增的项的集合,需要再对其进行拦截
switch(methods){
case'push':
case'unshift':
inserted=args
case'splice':
inserted=args.slice(2)//因为splice第二个参数后面的才是新增的
}
if(inserted){
ob.observerArray(inserted)
}
returnresult
}
})
原理总结
在面试中,如果我们需要手写vue的响应式原理,上面的代码足矣。但是我们通过学习vue的源码,如果在面试中能够给出以下加以总结性的回答更能得到面试官的青睐。
vue2.0源码的响应式原理:
- 因为使用了递归的方式对对象进行拦截,所以数据层级越深,性能越差
- 数组不使用Object.defineProperty的方式进行拦截,是因为如果数组项太多,性能会很差
- 只有定义在data里的数据才会被拦截,后期我们通过vm.newObj='xxx'这种在实例上新增的方式新增的属性是不会被拦截的
- 改变数组的索引和长度,不会被拦截,因此不会引起视图的更新
- 如果在data上新增的属性和更改数组的索引、长度,需要被拦截到,可以使用$set方法
- 可以使用Object.freeze方法来优化数据,提高性能,使用了此方法的数据不会被重写set和get方法
vue3.0源码响应式原理:
- 3.0版本中使用了proxy代替了Object.defineProperty,其有13中拦截方式,不需要对对象和数组分别进行处理,也无需递归进行拦截,这也是其提升性能最大的地方
- vue3.0版本响应式原理的简单实现
consthandler={
get(target,key){
if(typeoftarget[key]==='object'&&target[key]!==null){
returnnewProxy(target[key],handler)
}
returnReflect.get(target,key)
},
set(target,key,value){
if(key==='length')returntrue
console.log('update')
returnReflect.set(target,key,value)
}
}
constobj={
arr:[1,2,3],
count:{num:1}
}
//obj是代理的目标对象,handler是配置对象
constproxy=newProxy(obj,handler)
到此这篇关于详解实现vue的数据响应式原理的文章就介绍到这了,更多相关vue数据响应式内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!