探究一波vue2原理中的数据劫持~
-
背景:众所周知,vue2具有响应式的特征,也就是data中的数据发生变化,会引起视图的改变。那么data中的数据变化是怎么被vue监测到的呢?这就是今天要分享的vue响应式前篇—数据劫持。
-
何为数据劫持?
利用JavaScript引擎赋予的功能,检测对象属性变化,即:利用js的API将data中的数据变为 发生变化可被监测的,通俗讲就是数据发生了变化,会被某个东西捕获到,这就是数据劫持。 -
数据劫持有什么用?
一旦捕获到数据发生了变化,就可以在捕获同时,通知模板进行更新,达到真正的响应式,所以数据劫持就是响应式的基础。 -
如何实现数据劫持?
- 数据劫持必备课前知识:Object.defineProperty() 参考文档如下:
defineProperty参考文档
作用:Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
在数据劫持中,该API的主要作用就是为data中的属性添加getter和setter,数据发生变化,会被这两个函数监测到。- 数据劫持的必要成分
- observe函数
- Observer类
- defineReactive函数:该部分是对对象进行配置
- arrayMethods:该部分是对数组进行配置,未在图中画出(与defineReactive并列)
三部分的主要关系
细节实现:
-
defineReactive函数:会用到闭包(val)
function defineReactive(data,key,val){
if(arguments.length === 2) val = data[key]
let childOb = observe(val)
const dep = new Dep()
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get(){
console.log("试图访问"+key+"属性")
return val
},
set(newValue){
console.log("试图修改"+key+"属性")
if(val === newValue) return
val = newValue
childOb = observe(val)
}
})
}
- Observer类
// 该类对一个对象的某一层进行劫持配置
export default class Observer{
constructor(value){
//该方法就是为当前对象添加一个属性__ob__,并设置不可枚举
def(value,"__ob__",this,false)
if(Array.isArray(value)){
// 如果判断是数组,则强制修改其原型对象
Object.setPrototypeOf(value,arrayMethods) //arrayMethods在array.js中有定义
this.observerArray(value)
}else{
this.walk(value)
}
}
//对象:遍历一层数据做配置
walk(value){
for(let i in value){
defineReactive(value,i)
}
}
// 数组:遍历数据做配置
observerArray(value){
for(let i=0;i<value.length;i++){
observe[value[i]]
}
}
}
- observe函数
function (value){
if(typeof value !== 'object') return
let ob
// 判断对象中是否已经做过配置,__ob__是处理的标志
if(value.__ob__ !== undefined){
ob = value.__ob__
}else{
// 若没有做配置,则在这里进行配置
ob = new Observer(value)
}
return ob
}
- array.js文件:处理数组的劫持
const arrayPrototype = Array.prototype
// 数组中重写的方法
const methods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 以数组原型创建一个对象
export const arrayMethods = Object.create(arrayPrototype)
methods.forEach(methodName=>{
let orignal = arrayPrototype[methodName]
def(arrayMethods,methodName,function(){
let addArgs
let args = [...arguments]
//这些方法会往数组中添加新的数据,需要特殊处理
switch(methodName){
case 'push':
case 'unshift':
addArgs = arguments
break
case 'splice':
addArgs = args.slice(2)
}
if(addArgs){
this.__ob__.observerArray(addArgs)
}
const res = orignal.apply(this,arguments)
console.log("lalala");
return res
},false)
})