vue源码浅析对象和数组依赖处理

//先看一例子

importVuefrom'./instance/vue'
letv=newVue({
data:{
a:1,b:{
c:3
}
}
})
console.log(v.b.c);//3

v.$watch("b.c",(newVal,oldVal)=>console.log('newVal',newVal,'oldVal',oldVal,'\n'));//1秒后newVal{d:[Getter/Setter]}oldVal3

v.$watch("b.c.d",'\n'))//2秒后newVal5oldVal4


setTimeout(()=>{
v.b.c={d:4};
},1000)

setTimeout(()=>{
v.b.c.d=5
},2000)

click here 可运行的 example

(以下都是简化的代码,实际的源码复杂许多)首先是让vue本身对data引用,以及添加$watch方法:

importWatcherfrom'../watcher'
import{observe}from"../observer"
exportdefaultclassVue{
constructor(options={}){
this.$options=options
letdata=this._data=this.$options.data

//shallowiteratethroughkeystogivevuedirectreference
Object.keys(data).forEach(key=>this._proxy(key))

//deepiteratethroughallkeystoproxyallkeys
observe(data,this)
}

$watch(expOrFn,cb,options){
newWatcher(this,expOrFn,cb)
}
_proxy(key){
varself=this

Object.defineProperty(self,key,{
configurable:true,enumerable:true,get:functionproxyGetter(){
returnself._data[key]
},set:functionproxySetter(val){
self._data[key]=val
}
})
}

}

接着实现深度代理函数observe,递归代理所有属性,从而监测所有属性的变化:

import{def}from"../util"
importDepfrom"./dep"
exportdefaultclassObserver{
constructor(value){
this.value=value
this.walk(value)
}
//deepobserveeachkey
walk(value){
Object.keys(value).forEach(key=>this.convert(key,value[key]))
}
convert(key,val){
defineReactive(this.value,val)
}
}
exportfunctiondefineReactive(obj,val){
vardep=newDep()

//recursiveobserveallkeys
varchildOb=observe(val)

Object.defineProperty(obj,{
enumerable:true,configurable:true,get:()=>{
//checkdependencytosubscribe
if(Dep.target){
dep.addSub(Dep.target)
}
returnval
},set:newVal=>{
varvalue=val
if(newVal===value){
return
}
val=newVal
//checkchangetoreobserveandpublishalldependencies
childOb=observe(newVal)
dep.notify()
}
})
}

exportfunctionobserve(value,vm){
if(!value||typeofvalue!=='object'){
return
}
returnnewObserver(value)
}

实现依赖处理:

//import{toArray}from'../util/index'
letuid=0
/**
*Adepisanobservablethatcanhavemultiple
*directivessubscribingtoit.
*
*@constructor
*/
exportdefaultfunctionDep(){
this.id=uid++
this.subs=[]
}
//thecurrenttargetwatcherbeingevaluated.
//thisisgloballyuniquebecausetherecouldbeonlyone
//watcherbeingevaluatedatanytime.
Dep.target=null
/**
*Addadirectivesubscriber.
*
*@param{Directive}sub
*/
Dep.prototype.addSub=function(sub){
this.subs.push(sub)
}
/**
*Removeadirectivesubscriber.
*
*@param{Directive}sub
*/
Dep.prototype.removeSub=function(sub){
//this.subs.$remove(sub)
}
/**
*Addselfasadependencytothetargetwatcher.
*/
Dep.prototype.depend=function(){
Dep.target.addDep(this)
}
/**
*Notifyallsubscribersofanewvalue.
*/
Dep.prototype.notify=function(){
//stablizethesubscriberlistfirst
varsubs=(this.subs)
for(vari=0,l=subs.length;i<l;i++){
subs[i].update()
}
}

实现watch:

importDepfrom'./observer/dep'
exportdefaultclassWatcher{
constructor(vm,cb){
this.cb=cb
this.vm=vm
this.expOrFn=expOrFn
this.value=this.get()
}
update(){
this.run()
}
run(){
constvalue=this.get()
if(value!==this.value){//checkisEqualifnotthencallback
this.cb.call(this.vm,value,this.value)
this.value=value
}
}
addDep(dep){
dep.addSub(this)
}
get(){

this.beforeGet();
console.log('\n','watchget');
//fnorexpr
varres=this.vm._data,key=[];
console.log('expOrFn',this.expOrFn)

//towatchinstancelikea.b.c
if(typeofthis.expOrFn=='string'){
this.expOrFn.split('.').forEach(key=>{
res=res[key]//eachwillinvokegetter,sinceDep.targetistrue,thiswillsurelyaddthisintodep
})
}

this.afterGet();
returnres
}
}


/**
*Preparefordependencycollection.
*/
Watcher.prototype.beforeGet=function(){
Dep.target=this;
};
/**
*Cleanupfordependencycollection.
*/
Watcher.prototype.afterGet=function(){
Dep.target=null;
};

以上示例是可以无依赖直接运行的。

接下来是vue的源码片段;

一般对象的处理可以直接代理defineProperty就可以了,不过对于Array的各种操作就不管用了,所以vue进行了基本数组方法代理:

functiondef(obj,val,enumerable){
Object.defineProperty(obj,{
value:val,enumerable:!!enumerable,writable:true,configurable:true
})
}

functionindexOf(arr,obj){
vari=arr.length
while(i--){
if(arr[i]===obj)returni
}
return-1
}

constarrayProto=Array.prototype
exportconstarrayMethods=Object.create(arrayProto)

;[
'push','pop','shift','unshift','splice','sort','reverse'
]
.forEach(function(method){
//cacheoriginalmethod
varoriginal=arrayProto[method]
def(arrayMethods,method,functionmutator(){
//avoidleakingarguments:
//http://jsperf.com/closure-with-arguments
vari=arguments.length
varargs=newArray(i)
while(i--){
args[i]=arguments[i]
}
varresult=original.apply(this,args)
varob=this.__ob__
varinserted
switch(method){
case'push':
inserted=args
break
case'unshift':
inserted=args
break
case'splice':
inserted=args.slice(2)
break
}
//sameasobjectchange
//checkchangetoreobserveandpublishalldependencies
if(inserted)ob.observeArray(inserted)
//notifychange
ob.dep.notify()
returnresult
})
})


//es5defineProperty对于数组的某些操作和属性(如:length)变化代理有问题,所以需要使用定义的方法操作
具体原因看http://www.cnblogs.com/ziyunfei/archive/2012/11/30/2795744.html和
http://wiki.jikexueyuan.com/project/vue-js/practices.html

def(
arrayProto,'$set',function$set(index,val){
if(index>=this.length){
this.length=Number(index)+1
}
returnthis.splice(index,1,val)[0]//在里面还是通过代理方法splice实现
}
)

def(
arrayProto,'$remove',function$remove(item){
/*istanbulignoreif*/
if(!this.length)return
varindex=indexOf(this,item)
if(index>-1){
returnthis.splice(index,1)
}
}
)


看实际的observer干了什么:

constarrayKeys=Object.getOwnPropertyNames(arrayMethods)



exportfunctionObserver(value){
this.value=value
this.dep=newDep()
def(value,'__ob__',this)
if(isArray(value)){
varaugment=hasProto//hasProo='__prop__'in{};__prop__只在某些浏览器才暴漏出来
?protoAugment//直接将value.__proto__=src
:copyAugment//直接改变本身方法
augment(value,arrayMethods,arrayKeys)
this.observeArray(value)
}else{
this.walk(value)
}
}



functioncopyAugment(target,src,keys){
for(vari=0,l=keys.length;i<l;i++){
varkey=keys[i]
def(target,src[key])
}
}
functionprotoAugment(target,src){
target.__proto__=src
}

总的来说,vue是代理了原始数据的增删改查,从而进行事件订阅和发布等操作,从而控制数据流。

heavily inspired by:https://segmentfault.com/a/1190000004384515

相关文章

什么是设计模式一套被反复使用、多数人知晓的、经过分类编目...
单一职责原则定义(Single Responsibility Principle,SRP)...
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强...
适配器模式将一个类的接口转换成客户期望的另一个接口,使得...
策略模式定义了一系列算法族,并封装在类中,它们之间可以互...
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,...