(1)变更检测本系列主要分析和实现Vue源码流程,省略了源码中的一些细节。初始化ObjectChangedetectionArrayChangedetectionobserveflowchart1.初始化定义Vue构造函数,将操作方法??混合到Vue原型中,方便后面扩展在初始化函数->数据初始化中进行状态初始化//index.jsimport{initMixin}from"init.js"constVue=function(options){//选项初始化this._init(options);}//混合操作方法initMixin(Vue)intoVueprototype;...exportdefaultVue;//init.jsimport{initState}从“state.js”导出函数initMixin(Vue){Vue.prototype._init=function(options){constvm=this;vm.$options=选项;//初始化状态initState(vm);}}//state.js初始化状态import{observe}from'observe/index.js'exportfunctioninitState(vm){constopt=vm.$options;if(opt.data){//初始化数据initData(vm);}}functioninitData(vm){letdata=vm.$options.data;//判断数据是否为函数data=typeofdata==='function'?data.call(vm):数据;//在vm实例上重新挂载统一数据对象vm._data=data//数据检测与劫持observe(data);}2.对象变化检测Object.defineProperty()缺点无法检测新增和删除的属性2.1数据劫持//observe/index.js//检测/重写数据并返回可检测对象exportfunctionobserve(data){//不适用于非对象类型劫持if(typeof数据!='对象'||数据==null)返回;returnnewObserve(data);}//数据检测类Observe{constructor(data){this.walk(data);}//对象数据劫持——相当于重写性能瓶颈walk(){Object.keys.forEach(key=>{defineReactive(data,key,data[key])})}}//数据劫持publicmethodexportconstdefineReactive(target,key,value){Object.defineProperty(target,key,{enumerable:true,//默认也是trueconfigurable:true,//同上get(){returnvalue;//closure},set(newVal){if(newVal===value)return;value=newVal;}})}此时我们可以使用observe方法对传入的对象进行数据检测,劫持数据的值和变化但是数据是对_data的,为了让开发模型的语法尽可能简洁,这里需要使用dataproxy2.2。数据代理//state.js补充initData函数initData(vm){//...其他代码for(letkeyindata){proxy(vm,'_data',key);}}函数公关oxy(vm,target,key){Object.defineProperty(vm,key,{get(){returnvm[target][key];},set(newVal){vm[target][key]=newVal;}})}constvm=newVue({data(){return{name:"foo",age:11}}})当我们执行上面的代码时,我们可以读取vm上的name属性,name和age都有getter和setter2.3深度检测和新值检测当data中的值是一个嵌套对象,在给data属性设置对象值的时候,我们希望还是检测它,不要检测已经检测到的数据然后重写//observe/index.jsexportfunctionobserve(data){...//数据已经存在。检测到Observe实例if(data.__ob__instanceofObserve)returndata.__ob__...}classObserve{constructor(data){Object.defineProperty(data,"__ob__",{value:this,//这个属性直接使用实例赋值后数组检测需要enumerable:true//防止深度检测栈递归爆炸})...}}exportconstdefineReactive=function(target,key,value){//深度检测observe(value);Object.defineProperty(target,key,{...set(newVal){...//新值检测observe(value);...}})}3.数组变化检测。上面observe流程图中的hasMethod判断是指数组调用的方法是否在重写的列表中。数组一般数据有很多元素。如果一个一个检测下标,会浪费性能,因为相对于修改下标,我们经常使用数组的方式修改。注意,不是Object.defineProperty检测不到,而是Vue在设计时丢弃了它。除了检测下标,仍然使用observe检测数组中引用数据类型的元素,补充可以修改原数组的方法(原型链继承的方式)//observe/index.jsimport{newArrayProto}from"observe/array.js"...classObserve{constructor(data){...//数组类型判断if(Array.isArray(data)){//设置数据的原型对象Object.setPrototypeOf(data,newArrayProto)this.observeArray(data)}else{}}//数组劫持observeArray(data){//元素是递归劫持的数组/对象data.forEach(item=>observe(item))}}...//observe/array.jsletoriginArrayProto=Array.prototype//创建一个原型为originArrayProto的对象exportletnewArrayProto=Object.create(originArrayProto);//7种修改数组的方式constmethods=["push","pop",“取消”,"shift","sort","splice","re??verse"]methods.forEach(method=>{newArrayProto[method]=function(..args){//调用原始方法constresult=originArrayProto[method].apply(this,args);constob=this.__ob__;//__ob__就是上面添加的Observe实例//数据劫持newdataletinserted;switch(method){case'push':case'unshift':inserted=args;break;case'splice':inserted=args.slice(2);//第三个参数为新数据break;}//新数据检测if(inserted){ob.observeArray(inserted);}returnresult;}})通过原型链的继承,将中间层对象设置为原始数据的原型对象。它是一种面向切面编程的方法,只重写了部分方法,可以劫持新添加的元素数组(很优雅,有点恶心)
