当前位置: 首页 > Web前端 > HTML5

Vue.js响应式原理分析与实现

时间:2023-04-04 23:11:00 HTML5

很久以前就开始接触angularjs了。当时了解到angularjs通过脏检查实现数据监听和页面更新渲染。之后接触到了vue.js。当时我也很好奇vue.js是如何监听数据更新并重新渲染页面的。今天我们就来一步步分析vue.js响应式的原理,并实现一个简单的demo。首先,让我们回顾一些基础知识。基础知识Object.definePropertyes5增加了Object.definePropertyapi,可以让我们为对象属性设置getters和setters,从而可以劫持用户对对象属性的取值和赋值。例如下面的代码:constobj={};让val='cjg';Object.defineProperty(obj,'name',{get(){console.log('劫持了你的值操作');returnval;},set(newVal){console.log('劫持了你的赋值操作');val=newVal;}});console.log(obj.name);obj.name='cwc';控制台。日志(对象名称);我们通过Object.defineProperty劫持了obj[name]的取值和赋值操作,所以我们可以在这里做一些小动作,比如当obj[name]被赋值时触发一个更新页面的动作。发布-订阅模式发布-订阅模式是一种比较常见的设计模式,它有两个角色:发布者和订阅者。多个订阅者可以向同一个发布者订阅一个事件,当事件发生时,发布者通知所有订阅该事件的订阅者。让我们看一个例子来理解。classDep{constructor(){this.subs=[];}//添加订阅者addSub(sub){if(this.subs.indexOf(sub)<0){this.subs.push(sub);}}//通知订阅者notify(){this.subs.forEach((sub)=>{sub.update();})}}constdep=newDep();constsub={update(){console.log('sub1update')}}constsub1={update(){console.log('sub2update');}}dep.addSub(sub);dep.addSub(sub1);dep.notify();//通知订阅者事件并触发其更新函数动手实践了解了Object.defineProperty和发布-订阅者模式后,我们很容易想象vue.js是基于以上两者来实现数据监控的。Vue.js首先使用Object.defineProperty来劫持要监控的数据的getter和setter。当分配/检索数据的属性时,vue.js可以相应地检测和处理它。通过订阅发布模式,我们可以为对象的每个属性创建一个发布者。当其他订阅者依赖此属性时,该订阅者将被添加到发布者的队列中。利用Object.defineProperty的数据劫持,当调用属性的setter时,属性的发布者通知所有订阅者更新内容。接下来我们手工实现一下(详见注释):classObserver{constructor(data){//如果不是对象,则返回}这个.data=数据;这个。步行();}//传入数据的数据劫持walk(){for(letkeyinthis.data){this.defineReactive(this.data,key,this.data[key]);}}//创建当前属性的已发布实例,并使用Object.defineProperty对当前属性进行数据劫持。defineReactive(obj,key,val){//创建当前属性的发布者constdep=newDep();/**递归对子属性的值进行数据劫持,例如下面的数据*letdata={*name:'cjg',*obj:{*name:'zht',*age:22,*obj:{*name:'cjg',*age:22,*}*},*};对最外层的name和obj进行数据劫持,然后对obj对象的子属性obj.name、obj.age、obj.obj进行数据劫持,递归,直到所有数据完成数据劫持工作。*/新观察者(val);Object.defineProperty(obj,key,{get(){//如果当前存在对这个属性的依赖,则将其添加到发布者的订阅者队列中if(Dep.target){dep.addSub(Dep.target);}returnval;},set(newVal){if(val===newVal){return;}val=newVal;newObserver(newVal);dep.notify();}})}}//发布者添加所有将依赖该属性的watchers添加到subs数组中,当属性发生变化时,会调用所有依赖该属性的watchers的update函数触发更新。classDep{constructor(){this.subs=[];}addSub(sub){如果(this.subs.indexOf(sub)<0){this.subs.push(sub);}}notify(){this.subs.forEach((sub)=>{sub.update();})}}Dep.target=null;//观察者classWatcher{/***创建一个Watcher实例.*@param{*}vm*@param{*}keys*@param{*}updateCb*@memberofWatcher*/constructor(vm,keys,updateCb){this.vm=vm;this.keys=键;this.updateCb=updateCb;这个。值=空;这个.get();}//根据vm和keys获取最新的观察值get(){Dep.target=this;constkeys=this.keys.split('.');让价值=this.vm;keys.forEach(_key=>{value=value[_key];});this.value=值;Dep.target=null;返回这个值;}update(){constoldValue=this.value;constnewValue=this.get();if(oldValue!==newValue){this.updateCb(oldValue,newValue);}}}letdata={name:'cjg',obj:{name:'zht',},};newObserver(data);//监听data对象的name属性,当data.name发现变化时,触发cb函数newWatcher(data,'name',(oldValue,newValue)=>{console.log(oldValue,newValue);})data.name='zht';//监听data对象的obj.name属性,当data.obj.name发现变化时,触发cbFunctionnewWatcher(data,'obj.name',(oldValue,newValue)=>{console.log(oldValue,newValue);})data.obj.name='cwc';data.obj.name='dmh';结论这样一个简单的响应式数据监控就完成了。当然,这只是一个简单的demo来说明vue.js响应式的原理。真正的vue.js源码会更复杂,因为加入了很多其他的逻辑。接下来我可能会将它链接到html,实现v-model、computed和{{}}语法。代码地址如果有兴趣欢迎一起学习讨论。本文地址为->我的博客地址,欢迎开始或关注