前端小白-vue响应式原理-苦学史-附视频

192 阅读6分钟
原文链接: mp.weixin.qq.com

如何获取同步视频:关注微信公众号“惜时科技”,发送”vue响应式原理“,皆可观看下载”Vue响应式原理“的同步视频。

完成数 据侦听

准备工作

新建html,div#app,input标签,{{}}模板;

新建构造函数Vue,往Vue构造函数中传入对象options;

实例化一个Vue对象vm,传入options

defineProperty与侦听器属性:

对象的普通属性与访问器属性。

普通属性直接定义

访问器属性通过defineProperty来定义属性

每个访问器属性内部有两个函数:set函数,get函数。

给访问器属性赋值,其实是调用内部set函数,新值会成为set函数的形式参数。

获取访问器属性,其实是调用内部get函数,值为get函数的返回值。

通过defineProperty将options.data中所有的属性赋值给vm,让vm下的属性变为访问器属性。

在控制台进行测试

调用vm.msg触发get

给vm.msg赋值,触发set

封装一个observer函数,将定义侦听器属性的代码提取到observer中

遍历模板

在options中添加挂载节点el

在vue构造函数中获取挂载节点

使用while循环来遍历el下的子节点,使用第一个子节点是否存在来作为循环的结束条件,创建文档片段,通过循环将el中的子节点循环插入到文档片段中,当移除最后一个el下的子节点时,循环就结束了,el.childNodes[0]为undefined,退出循环:

再将文档片段插入到el中

判断子节点的类型

如果nodeType=1则为元素节点,寻找元素节点中的属性节点列表attributes是否含有节点名称nodeName为v-model属性,如果含有,则给它绑定input事件,并且将event.target.value赋值给vm对应的属性,比如v-mode属性节点对应的节点属性值nodeValue的值是msg,则赋值给vm.msg:

测试:input中输入值,将会触发msg内部的set函数,msg可以得到输入的值,实现视图更新触发数据更新:

如果nodeType=3则为文本节点,寻找文本节点中是否包含模板{{msg}}

定义正则来匹配{{msg}}

将vm的msg值赋值给文节点中的节点属性nodeValue,RegExp.$1获取匹配的值,这里匹配得到的值为‘msg’,$1为(.*)中匹配的值,就是我们的msg

测试,刷新浏览器,即可得到msg的值,{{msg}}已经变成了hello vue

,并且控制台,打印触发get了

将遍历模板的代码封装成一个compile函数

发布订阅者模式

新建订阅者Watcher构造函数

传入vm,child,vmKey

新建update函数来更新child.nodeValue

Compile中的更新child.nodeValue的值换成使用订阅者来更新:

在订阅者中执行它的update函数实现更新

新建一个主题构造函数,定义一个用来存储订阅者的数组subs:

添加一个addSub方法用来添加订阅者:

添加notify用来给所有订阅者发布更新消息,update为订阅者的方法,下面会有定义:

把每一个侦听器属性当做一个主题,如msg,在模板中每使用一次侦听器属性,则为订阅了一次该主题,所以我们需要在每生成一个侦听器属性时,实例化一个主题对象:

为了能将msg主题的订阅者new Watcher添加到subs中,我们将new Watcher赋值给Dep下的一个属性target:

执行完,Dep.target=this之后,执行this.update,在update中会触发msg的get函数,所以此时,我们在get函数中收集msg的订阅者:

当我们在输入框中更新了msg值时,我们要通知所有的订阅了msg这个主题的订阅者来更新它们的nodeValue,因为触发input事件时,我们会触发msg内部的set函数,所以我们在set函数中调用msg主题对象的notify函数,来通知所有订阅者的调用它们的update方法更新它们的nodeValue。

执行流程总结:

创建一个Vue实例化对象vm = new Vue。

执行Vue构造函数。

执行observer函数

将options中的data中属性变成vm下的侦听器属性。

获取options中的挂载节点el

将挂载节点赋值给vm下的$el

执行compile函数,遍历el下的html模板

寻找v-model=”msg”和{{msg}}

如果发现含有v-model=”msg”的input元素,则给input元素绑定一个input事件,并且将event.target.value的值赋值给vm[msg],也就是当输入框的值一更新,vm[msg]同步更新了。

如果发现含有{{msg}}的节点,则将vm[msg]的值赋值给节点的nodeValue,但是直接赋值,我们只能在首次运行时将vm[msg]的值赋值给节点的nodeValue,当vm[msg]的值更新时,节点中nodeValue不能同步更新。

为了能保证vm[msg]更新,节点的nodeValule同步更新

我们采用发布订阅者模式,将msg当成主题,而含有{{msg}}的节点则为订阅者,当vm[msg]一更新,则通知我们的订阅者更新它的nodeValue。

新建一个订阅者Watcher构造函数

在遍历模板时,发现某节点有{{msg}}则实例化一个订阅者new Watcher,在watcher中将更新节点的nodeValue

在更新nodeValue会调用vm[msg]则会触发msg内部的get函数

在get函数中,我们将订阅者new Watcher存储到一个数组中,为了能将想new Watcher存储到一个数组,我们需要在Watcher构造函数中,将new Watcher赋值给全局变量,为了方便我们将该全局变量设置为Dep.target,然后在msg的内部的get函数中将该Dep.target存储到数组中。

此时我们需要创建一个主题构造函数Dep

在Dep内部添加一个数组subs用来存储订阅者,在Dep的原型下添加一个addSubs方法,用来将订阅者添加到subs中,添加一个notify函数,在notify函数中遍历subs中所有的订阅者,让每个订阅者执行它们的update函数,用来更新订阅者的nodeValue。

每当含有v-model=’msg’的input元素执行input事件时

vm[msg]会更新值,此时就会触发msg内部的set函数,在set函数中我们执行notify函数,通知所有订阅了msg主题的订阅者,msg已经更新了,它们应该执行自己的update方法,更新自己nodeValue了,此时是第二次调用update方法,我们会触发msg的内部get函数,我们会再次将该订阅者Dep.target添加到数组subs中,此时就出现了两个一模一样的订阅者,也就是每次执行update一次,Dep.target就会被添加到subs中。

为了避免重复的添加Dep.target

我们在Watcher的构造函数中,执行update函数后,将Dep.target设置为null,此时我们在msg的get函数中,做一个判断,当Dep.target不为null时,才添加到subs数组中。