在Vue中,最核心的知识点之一就是数据响应原理。数据响应性原理由两部分组成:检测数据变化,依赖于收集,理解这两个知识点,就可以理解数据响应性原理的本质。1.检测数据变化能够监听到帧中的数据变化是数据响应性原理的前提,因为数据响应性是基于检测数据变化,然后触发一系列的更新操作。本次基于Vue2.x介绍数据响应式的原理,主要是利用Object.defineProperty()将数据转化为可检测的数据。1.1非数组对象下面是一个非数组对象的例子constobj={a:{m:{n:5}},b:10};观察上面的对象,可以发现存在包含关系(即一个对象可能包含另一个对象),那么很自然的就会想到递归实现。Vue中为了保证代码的高可读性,引入了三个模块来实现这个逻辑:observe、Observer、defineReactive,调用关系如下图所示:1.1.1函数observe是框架监听的入口文件数据变化。通过调用该函数,一方面触发其框架监听对象数据变化的能力;另一方面,它定义了何时递归到终止条件的最内层。importObserverfrom'./Observer';exportdefaultfunction(value){//如果value不是对象,什么都不做(说明递归是基本类型,它的变化可以被框架监听)if(typeofvalue!=='object'){return;}//Observer实例letob;//__ob__是value上的一个属性,它的值为对应的Observer实例(说明已经处于帧监听状态)if(typeofvalue.__ob__!=='undefined'){ob=value.__ob__;}else{//是一个对象,属性还是监听不到状态ob=newObserver(value);}returnob;}1.1.2目的Observer这个功能主要有两个:一个是将实例挂载到object值的__ob__属性上(observe中会用到这个属性,通过判断是否有这个属性来判断是否已经处于帧监听状态);另一种是遍历对象上的所有属性,然后使属性frame-listenable(通过调用defineReactive)。exportdefaultclassObserver{constructor(value){//给实例添加__ob__属性def(value,'__ob__',this,false);//判断是数组还是对象if(!Array.isArray(value)){//如果是对象,遍历它,把它上面的属性变成响应式的this.walk(value);}}//遍历对象上的属性,变成响应式的walk(value){for(letkeyinvalue){defineReactive(value,key);}}}1.1.3defineReactive该方法主要是将Object.defineProperty封装成一个函数。之所以做这一步是因为Object.defineProperty在设置set属性的时候需要一个临时变量来存放变化前的值,通过封装引入val,利用闭包的思想,这样就不需要设置临时变量了函数外的变量。exportdefaultfunctiondefineReactive(data,key,val){if(arguments.length===2){val=data[key];}//需要观察子元素,至此形成一个递归letchildOb=observe(val);Object.defineProperty(data,key,{//enumerableenumerable:true,//configurableconfigurable:true,//getterget(){console.log(`access${key}property`);returnval;},//setterset(newValue){console.log(`Changethepropertyof${key}to${newValue}`);if(val===newValue){return;}val=newValue;//设置新值时,new的值也要观察childOb=observe(newValue);}});}1.2数组Object.defineProperty不能直接监听数组内部的变化,那么数组的内容应该怎么变化呢?Vue主要采用修改数组的方式(push、pop、shift、unshift、splice、sort、reverse),在保留其原有功能的前提下,使其新增的项具有响应性。//array.js文件//获取Array的原型constarrayPrototype=Array.prototype;//创建一个以Array.prototype为原型的arrayMethods对象,并暴露exportconstarrayMethods=Object.create(arrayPrototype);//7个数组为重写方法constmethodsNeedChange=['push','pop','shift','unshift','splice','sort','reverse'];methodsNeedChange.forEach(methodName=>{//备份原方法constoriginal=arrayMethods[methodName];//定义新方法def(arrayMethods,methodName,function(){//恢复原函数constresult=original.apply(this,arguments);//将类数组对象转换为数组constargs=[...arguments];//数组不会是最外层,所以Observer实例已经加入其中constob=this.__ob__;//push/unshift/splice会插入新的item,插入的新item需要改变观察letinserted=[];switch(methodName){case'push':case'unshift':{inserted=args;break;}case'splice':{inserted=args.slice(2);break;}}//对于有插入项的,if(inserted.length){ob.observeArray(inserted);}ob.dep.notify();返回结果;},假);});Observer函数除了修改其原有的数组方法外,还会增加对数组的处理逻辑。exportdefaultclassObserver{constructor(value){//给实例添加__ob__属性def(value,'__ob__',this,false);//判断是数组还是对象if(Array.isArray(value)){//改变数组原型为新修改的内容Object.setPrototypeOf(value,arrayMethods);//让这个数组变成observethis.observeArray(value);}else{//如果是对象,遍历它,改变属性onittoReactivethis.walk(value);}}//遍历对象上的属性,变成响应式walk(value){for(letkeyinvalue){defineReactive(value,key);}}//特殊遍历ofarraysobserveArray(arr){for(leti=0,l=arr.length;i
