数据响应式处理查看源码解决问题vm.msg={count:0},重新赋值属性msg,是否响应式?(是)vm.arr[0]=4,给数组元素赋值,视图会更新吗?(不是)vm.arr.length=0,修改数组长度,视图会更新吗?(not)vm.arr.push(4),view会更新吗?(是的)响应式处理入口响应式处理入口在initState中initState方法在src/core/instance/state.js中实现方法中重要的方法是observer...//parameter:options中的data参数2表示根数据(根数据的附加处理)observe(data,true/*asRootData*/)...解析observe方法observe方法定义在src/core/observer/index.js中。如果当前数据有观察者,则直接返回,不创建属性观察者,并返回判断数据对象,如果有__ob__属性,则返回观察者。如果没有,则newObserver(data)(内部让它响应式,注册getter/setter),然后返回observe中的getter收集依赖,setter会分发更新(第一次编译会触发getter)Dep.target在watcher类中定义,引用mountComponent方法,方法中会新建Watcherexport函数observe(value:any,asRootData:?boolean):Observer|void{//判断value是对象还是VNode实例if(!isObject(value)||valueinstanceofVNode){return}letob:Observer|void//如果值有__ob__(Observer类的属性)end//已经做了响应式就不用再操作了,相当于加了个缓存if(hasOwn(value,'__ob__')&&value.__ob__instanceofObserver){ob=value.__ob__}elseif(shouldObserve&&!isServerRendering()&&(Array.isArray(value)||isPlainObject(value))&&Object.isExtensible(value)&&!value._isVue){//创建一个观察者对象,ob=newObserver(value)}//根数据data将被标记为++extraif(asRootData&&ob){ob.vmCount++}//ReturnanObserverclassreturnob}解析Observer类会为值数据定义__ob__属性,值为Observer,不可枚举。初始化Dep实例,每个Observer对应一个Dep实例,用于以后遍历value,为其内部所有属性设置getter,设置setter时不考虑该属性。//this引用Observerdef(value,'__ob__',this)来判断value的数据类型。如果是数组,做数组响应式处理。对象调用walk方法遍历对象属性以添加getter、setter和依赖注入并分发更新。if(Array.isArray(value)){//当前浏览器是否支持对象原型__proto__//修补数组中的原型方法,如果数组发生变化,调用notify更新视图if(hasProto){protoAugment(value,arrayMethods)}else{copyAugment(value,arrayMethods,arrayKeys)}//遍历数组的所有元素,将是对象的元素转换为响应式对象,而不是所有属性!!!!//下标和长度是属性,由于性能问题无法通过vm.msg[0]更新//可以使用vm.msg.splice(0,1,100)代替this.observeArray(value)}else{//遍历对象中的每个属性都转换成一个setter/getterthis.walk(value)}defineReactive方法内部解析walk方法,调用方法中的shallow参数用于对对象属性做响应式处理,true表示浅响应,non-shallow需要递归设置属性Responsively为对象属性创建一个Dep实例,用于在get方法中收集依赖关系,并在set方法中调度更新在被劫持的对象属性的get和set中,它会判断是否用户为属性设置了getter/setter,如果设置了,也将被调用。setdependencycollection分析dep.target静态对象存在,将Watcher添加到dep的subs数组中get:functionreactiveGetter(){constvalue=getter?getter.call(obj):val//如果当前有一个依赖的target,即watcher对象,构建依赖关系if(Dep.target){//dep是一个收集当前属性依赖关系的对象。将当前watcher对象收集到当前属性dep的subs数组中dep.depend()//收集当前属性对应值的依赖。如果value是对象,调用Observer值对应的dep//这里的dep是递归的observer的值收集依赖,和上面的dep不同//例如data:{msg:{name:"zhangsan"}},这里给name添加依赖if(childOb){childOb.dep.depend()//如果属性值是数组,需要特殊处理收集数组对象依赖如data:{arr:[1,2,3]}该方法处理[1,2,3]//外层处理arrif(Array.isArray(value)){dependArray(value)}}}returnvalue}letchildOb=!shallow&&observe(val)Dep.target赋值在哪里?第一次渲染时,内部会分配newWatcher,内部pushTarget分配的Dep.target是全局唯一的。一个组件对应一个watcher,一个watcher对应一个Dep.target。当一个组件被挂载时,一个watcher将被创建并分配给Dep。targetpush入栈是因为,如果有嵌套组件,(watcher是嵌套的,先把父watcher入栈)执行子watcher,执行完成后从栈中弹出,执行父组件renderingDep.target=nullconsttargetStack=[]exportfunctionpushTarget(target:?Watcher){//为什么要压栈呢?//每个组件对应一个watcher对象(每个组件都有一个mountComponent)//如果a组件嵌套了b组件,渲染时发现有子组件,则先渲染b子组件,并且a组件的渲染进程会先挂载起来//a组件对应的watcher对象存放在栈中,(栈特性先进后出)//当b子组件渲染完成时,会pop从栈中向上,然后执行父组件渲染targetStack.push(target)Dep.target=target}导出函数poptarget(){//pop操作targetStack.pop()Dep.target=targetStack[targetStack.length-1]}这里赋值了,这里会触发数据劫持的get方法呢?第一次渲染会在给Dep.target赋值后调用updateComponent方法生成虚拟dom更新viewupdateComponent=()=>{//_render或者获取虚拟dom_update将虚拟dom转换为真实domvm._update(vm._render(),hydrating)}在_render方法中,会触发数据劫持的get方法。可以调试渲染相关方法:_c方法是createElement方法h函数,_s方法是toString(),_v方法是创建文本虚拟节点createTextVNode访问如图msg属性会触发data中的get方法劫持,从而在get方法中收集依赖,将msg属性的Dep收集到当前组件的watcher中定义的dep的subs数组中,如果已经收集到则不再重复收集(即在datamsg在视图中被使用了两次,只会收集一次依赖)因此,get中的第一次依赖收集是在第一次编译时触发的!
