关注公众号“放风筝的人”,回复“信息”获取500G信息(全“军”),更有专业交流群等你一起来。(哈哈)在Vue中,最核心的知识点之一就是数据响应原理。数据响应性原理包括两部分:检测数据变化和依赖采集。理解了这两个知识点就理解了数据响应性原理的本质。1.检测数据变化能够监听到帧中的数据变化是数据响应性原理的前提,因为数据响应性是基于检测数据变化,然后触发一系列的更新操作。本次基于Vue2.x介绍数据响应式的原理,主要是利用Object.defineProperty()将数据转化为可检测的数据。1.1非数组对象下面以非数组对象为例constobj={a:{m:{n:5}},b:10};观察上面的对象,我们可以发现存在包含关系(即一个对象可能包含另一个对象),那么很自然的就会想到递归实现。在Vue中,为了保证代码的高可读性,引入了三个模块来实现这个逻辑:observe、Observer、defineReactive,调用关系如下图:1.1.1函数observe是框架监听的入口文件数据变化。通过调用该函数,一方面触发其框架监听对象数据变化的能力;另一方面,它定义了何时递归到最内层的终止条件。从'./Observer'导入观察者;exportdefaultfunction(value){//如果value不是对象,什么都不做(说明递归对象是基本类型,它的变化可以被框架监听)if(typeofvalue!=='object'){返回;}//观察者实例letob;//__ob__是value上的一个属性,它的值是对应的Observer实例(说明已经处于帧监听状态)if(typeofvalue.__ob__!=='undefined'){ob=value.__ob__;}else{//Ob=newObserver(value);}returnob;}1.1.2Observer这个函数主要有两个目的:一个是将实例挂载到object值的__ob__属性上(observe中用到这个属性,判断是否已经在通过判断是否有该属性判断帧监听状态);另一种是遍历对象上的所有属性,然后使属性可框架化(通过调用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设置setAttributes需要一个临时变量来存放变化前的值。通过封装,利用闭包的思想,引入了val,这样就不需要在函数外设置临时变量了。导出默认函数defineReactive(data,key,val){if(arguments.length===2){val=data[key];}//需要观察子元素,从而形成递归letchildOb=observe(val);Object.defineProperty(data,key,{//可枚举的enumerable:true,//可配置的configurable:true,//getterget(){console.log(`access${key}property`);returnval;},//setterset(newValue){console.log(`将${key}的属性更改为${newValue}`);if(val===newValue){return;}val=newValue;//当一个新的value设置了,这个新值也要观察childOb=observe(newValue);}});}1.2数组Object.defineProperty不能直接监听数组内部的变化,那么数组内容应该怎么变化呢?Vue主要采用修改数组方式(push、pop、shift、unshift、splice、sort、reverse)的方式,使其新增的item在保留原有功能的情况下成为响应式的。//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将插入新项,需要Letinserted=[];switch(methodName){case'push':case'unshift':{inserted=args;break;}case'splice':{插入=args.slice(2);休息;}}//对于那些有插入项的,让新项成为响应式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{//如果是对象,遍历它,把它放在对象的属性上,变成响应式this.walk(value);}}//遍历对象上的属性,将其转化为反应式walk(value){for(letkeyinvalue){defineReactive(value,key);}}//特殊数组遍历observeArray(arr){for(leti=0,l=arr.length;i
