如何获取同步视频:关注微信公众号“惜时科技”,发送”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数组中。