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

Vue源码分析:双向绑定的原理

时间:2023-04-02 17:31:26 HTML

通过阅读Vue2.0的源码,想写写自己的理解,但是能力有限,所以从第一次投稿开始阅读游先生2016年4月11日,我要陆续写一篇:模板字符String转AST语法树AST语法树转渲染函数Vue双向绑定原理Vue虚拟dom对比原理里面有自己的理解和出处代码分析,尽量通俗易懂!由于是最早提交的2.0,与最新版本存在诸多差异和bug,后续会补充,敬请谅解!有中文注释的vue源码已经上传了。。。说双向绑定之前,先说说单向数据流的概念。参考Vuex官网上的一张图:这是单向数据流的极简表现。即状态(数据源)映射到视图,视图(用户输入)的改变触发行为,行为改变状态。但在实际开发中,大多数情况下,多个视图依赖同一个状态,多个行为影响同一个状态。Vuex的处理是将公共状态抽取出来,转换成单向数据流。另外,Vue的父子组件中的prop值传递也使用了单向数据流的概念,即父级prop的更新会向下流向子组件,反之则不然。react和vue都提倡单向数据流的管理状态,那么我们今天要讲的双向绑定是否违背了单向数据流的概念呢?我不这么认为。从上一篇文章中了解到,AST语法树被转移到了render函数中。Vue的双向绑定本质上是对oninput/onchange事件监听的值和语法糖的单向绑定。这种机制在一些需要实时反馈用户输入的场合非常方便。它只是通过在Vue内部封装action形成的。那么今天我们要说的是,状态的变化是如何引起视图的变化的呢?第一个难点是如何监控状态变化。Vue2.0主要使用defineProperty,但是有一个缺点就是无法检测到对象和数组的变化。游先生说3.0会用到proxy,但是兼容性还是个问题。有兴趣的同学可以了解一下;另一个难点是状态改变后如何触发view的改变。Vue2.0采用的发布/订阅模型,即每个状态都会有自己的订阅中心,每个订阅者都放在这里,订阅者有一个关于dom的更新功能。当状态改变时会发布一条消息:我改变了!订阅中心会一一告诉订阅者,订阅者知道后会执行自己的更新函数。源码分析今天涉及的代码都在observer文件夹下。流程大致如下:functionVue(options){//...vardata=options.data;数据=数据类型==='函数'?数据():数据||{};观察(数据,这个);观察者(这个,这个。渲染,这个。_更新);//...}首先对数据进行数据劫持(observe),然后为当前实例创建一个订阅者(Watcher)。如何实现,下面会一一说明。数据劫持数据劫持的本质是利用defineProperty重写对象属性的getter/setter方法。但是,由于defineProperty无法监听对象和数组的内部变化,当子属性为对象时,会递归观察该属性,直到达到简单数据类型;当是数组时,处理就是重写push、pop、shift等方法。通知订阅中心:状态已更改!通过这种方式,可以监控所有类型的数据。先来看看入口函数observe():functionobserve(value,vm){//如果检测到的数据不是对象,则退出变量对象;if(value.__ob__&&value.__ob__instanceofObserver){ob=value.__ob__;}else{ob=newObserver(值);}returnob;}observe()方法尝试为value创建一个观察者实例,并返回一个新的观察者或现有的观察者。下面会提到__ob__属性,即对象被观察后会有一个__ob__属性,用于存放观察者实例。我们再来看看Observer类:functionObserver(value){this.value=value;//通过defineProperty为value对象添加__ob__属性def(value,'__ob__',this);//专门处理数组if(Array.isArray(value)){value.__proto__=arrayMethods;value.forEach(item=>{observe(item);})}else{this.walk(value);}}显然,Observer类排除了properties的定义,这是对数组的特殊处理。处理的方法就是通过原型链修改数组的push、pop、shift……等方法。当然还需要observe()数组的每一个元素,因为数组元素也可能是对象,我们继续劫持,直到基本类型!我们先看看arrayMethods是如何修改这些方法的:constarrayProto=Array.prototype;exportconstarrayMethods=Object.create(arrayProto);['push','pop','shift','unshift','splice','sort','reverse'].forEach(method=>{//获取对应的native方法varoriginal=arrayProto[method];def(arrayMethods,method,()=>{//参数处理vari=arguments.length;varargs=newArray(i);while(i--){args[i]=arguments[i];}//运行本地方法varresult=original.apply(this,args);varob=this.__ob__;//特殊处理数组插入方法varinserted;switch(method){case'push':inserted=args;break;case'unshift':inserted=args;break;case'splice':inserted=args.slice(2);break;}//对插入的参数执行数据劫持if(inserted)ob.observeArray(inserted);//postchangenotificationob.dep.notify();returnresult;})})可以看到arrayMethods结构其实很简单。首先根据数组的原型创建一个新的对象,然后将数组的方法一一重写。方法重写的重点是:继续监听插入方法(push、unshift、splice)传入的新数据数组方法在调用时会强行触发通知:这里dep.notify(),defineProperty无法监听的问题数组内部变化解决了是的,当然,通过数组下标修改内部数据,你还是察觉不到!我们继续看walk()函数:Observer.prototype.walk=function(obj){varkeys=Object.keys(obj);对于(vari=0,l=keys.length;i