Vue数据双向绑定原理实例解析


Posted in Javascript onMay 15, 2020

Vue数据双向绑定原理是通过数据劫持结合发布者-订阅者模式的方式来实现的,首先是对数据进行监听,然后当监听的属性发生变化时则告诉订阅者是否要更新,若更新就会执行对应的更新函数从而更新视图

Vue数据双向绑定原理实例解析

MVC模式

以往的MVC模式是单向绑定,即Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新

Vue数据双向绑定原理实例解析

MVVM模式

MVVM模式就是Model?View?ViewModel模式。它实现了View的变动,自动反映在 ViewModel,反之亦然。对于双向绑定的理解,就是用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。再说细点,就是在单向绑定的基础上给可输入元素input、textare等添加了change(input)事件,(change事件触发,View的状态就被更新了)来动态修改model。

Vue数据双向绑定原理实例解析

双向绑定原理

vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的

我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。

因此接下去我们执行以下3个步骤,实现数据的双向绑定:

(1)实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

(2)实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图。

(3)实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。

Vue数据双向绑定原理实例解析

实现一个Observer

Observer是一个数据监听器,其实现核心方法就是Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理
如下代码实现了一个Observer。

function Observer(data) {  this.data = data;  this.walk(data);
}
 
Observer.prototype = {  walk: function(data) {    
var self = this;    //这里是通过对一个对象进行遍历,对这个对象的所有属性都进行监听
 Object.keys(data).forEach(function(key) {
 self.defineReactive(data, key, data[key]);
 });
 },  defineReactive: function(data, key, val) {    
 var dep = new Dep();   // 递归遍历所有子属性
 var childObj = observe(val);    
 Object.defineProperty(data, key, {      
 enumerable: true,      
 configurable: true,      
 get: function getter () {        
 if (Dep.target) {         
 // 在这里添加一个订阅者
 console.log(Dep.target)
 dep.addSub(Dep.target);
 }        return val;
 },     
 // setter,如果对一个对象属性值改变,就会触发setter中的dep.notify(),
 通知watcher(订阅者)数据变更,执行对应订阅者的更新函数,来更新视图。
 set: function setter (newVal) {        
 if (newVal === val) {          
 return;
 }
 val = newVal;       
 // 新的值是object的话,进行监听
 childObj = observe(newVal);
 dep.notify();
 }
 });
 }
};function observe(value, vm) {  if (!value || typeof value !== 'object') {    
return;
 }  return new Observer(value);
};// 消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后在属性变化的时候执行对应订阅者的更新函数
function Dep () {  
this.subs = [];
}
Dep.prototype = { /**
 * [订阅器添加订阅者]
 * @param {[Watcher]} sub [订阅者]
 */
 addSub: function(sub) {    
 this.subs.push(sub);
 }, // 通知订阅者数据变更
 notify: function() {    
 this.subs.forEach(function(sub) {
 sub.update();
 });
 }
};
Dep.target = null;

在Observer中,当初我看别人的源码时,我有一点不理解的地方就是Dep.target是从哪里来的,相信有些人和我会有同样的疑问。这里不着急,当写到Watcher的时候,你就会发现,这个Dep.target是来源于Watcher。

实现一个Watcher

Watcher就是一个订阅者。用于将Observer发来的update消息处理,执行Watcher绑定的更新函数。

如下代码实现了一个Watcher

function Watcher(vm, exp, cb) {  
this.cb = cb;  
this.vm = vm;  
this.exp = exp;  
this.value = this.get(); // 将自己添加到订阅器的操作}
 
Watcher.prototype = {  update: function() {    
this.run();
 },  run: function() {    
 var value = this.vm.data[this.exp];    
 var oldVal = this.value;    
 if (value !== oldVal) {      
 this.value = value;      
 this.cb.call(this.vm, value, oldVal);
 }
 },  get: function() {
 Dep.target = this; // 缓存自己
 var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
 Dep.target = null; // 释放自己
 return value;
 }
};

在我研究代码的过程中,我觉得最复杂的就是理解这些函数的参数,后来在我输出了这些参数之后,函数的这些功能也容易理解了。vm,就是之后要写的SelfValue对象,相当于Vue中的new Vue的一个对象。exp是node节点的v-model或v-on:click等指令的属性值。

上面的代码中就可以看出来,在Watcher的getter函数中,Dep.target指向了自己,也就是Watcher对象。在getter函数中,

var value = this.vm.data[this.exp] // 强制执行监听器里的get函数。
这里获取vm.data[this.exp] 时,会调用Observer中Object.defineProperty中的get函数
get: function getter () {        
if (Dep.target) {         
// 在这里添加一个订阅者         
console.log(Dep.target)          
dep.addSub(Dep.target);        
}        
return val;      
},

从而把watcher添加到了订阅器中,也就解决了上面Dep.target是哪里来的这个问题。

实现一个Compile

Compile主要的作用是把new SelfVue 绑定的dom节点,(也就是el标签绑定的id)遍历该节点的所有子节点,找出其中所有的v-指令和" {{}} ".

(1)如果子节点含有v-指令,即是元素节点,则对这个元素添加监听事件。(如果是v-on,则node.addEventListener('click'),如果是v-model,则node.addEventListener('input'))。接着初始化模板元素,创建一个Watcher绑定这个元素节点。

(2)如果子节点是文本节点,即" {{ data }} ",则用正则表达式取出" {{ data }} "中的data,然后var initText = this.vm[exp],用initText去替代其中的data。实现一个MVVM

可以说MVVM是Observer,Compile以及Watcher的“boss”了,他需要安排给Observer,Compile以及Watche做的事情如下

(1)Observer实现对MVVM自身model数据劫持,监听数据的属性变更,并在变动时进行notify

(2)Compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数

(3)Watcher一方面接收Observer通过dep传递过来的数据变化,一方面通知Compile进行view update。
最后,把这个MVVM抽象出来,就是vue中Vue的构造函数了,可以构造出一个vue实例。最后写一个html测试一下我们的功能

<!DOCTYPE html><html lang="en"><head>
 <meta charset="UTF-8">
 <title>self-vue</title></head><style>
 #app {    
 text-align: center;
 }</style><body>
 <div id="app">
 <h2>{{title}}</h2>
 <input v-model="name">
 <h1>{{name}}</h1>
 <button v-on:click="clickMe">click me!</button>
 </div></body><script src="js/observer.js"></script>
 <script src="js/watcher.js"></script>
 <script src="js/compile.js"></script>
 <script src="js/mvvm.js"></script>
 <script type="text/javascript">
 var app = new SelfVue({    
 el: '#app',    
 data: {      
 title: 'hello world',      
 name: 'canfoo'
 },    
 methods: {      
 clickMe: function () {        
 this.title = 'hello world';
 }
 },    
 mounted: function () {      
 window.setTimeout(() => {        
 this.title = '你好';
 }, 1000);
 }
 });</script></html>

先执行mvvm中的new SelfVue(...),在mvvm.js中, 

observe(this.data);
new Compile(options.el, this);

先初始化一个监听器Observer,用于监听该对象data属性的值。

然后初始化一个解析器Compile,绑定这个节点,并解析其中的v-," {{}} "指令,(每一个指令对应一个Watcher)并初始化模板数

据以及初始化相应的订阅者,并把订阅者添加到订阅器中(Dep)。这样就实现双向绑定了。

如果v-model绑定的元素,

<input v-model="name"> 

即输入框的值发生变化,就会触发Compile中的

node.addEventListener('input', function(e) {      
var newValue = e.target.value;      
if (val === newValue) {        
return;
 }      
 self.vm[exp] = newValue;
 val = newValue;
 });

self.vm[exp] = newValue;这个语句会触发mvvm中SelfValue的setter,以及触发Observer对该对象name属性的监听,即Observer中的Object.defineProperty()中的setter。

setter中有通知订阅者的函数dep.notify,Watcher收到通知后就会执行绑定的更新函数。

最后的最后就是效果图啦:

Vue数据双向绑定原理实例解析

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQuery 无废话系列教程(二) jquery实战篇上
Jun 23 Javascript
jquery formValidator插件ajax验证 内容不做任何修改再离开提示错误的bug解决方法
Jan 04 Javascript
JQueryEasyUI datagrid框架的进阶使用
Apr 08 Javascript
鼠标移到div,浮层显示明细,弹出层与div的上边距左边距重合(示例代码)
Dec 14 Javascript
javascript格式化json显示实例分析
Apr 21 Javascript
js实现类bootstrap模态框动画
Feb 07 Javascript
BootStrap select2 动态改变值的方法
Feb 10 Javascript
Thinkphp5微信小程序获取用户信息接口的实例详解
Sep 26 Javascript
Vue.set()动态的新增与修改数据,触发视图更新的方法
Sep 15 Javascript
简谈创建React Component的几种方式
Jun 15 Javascript
浅谈TypeScript的类型保护机制
Feb 23 Javascript
vue如何批量引入组件、注册和使用详解
May 12 Vue.js
JavaScript鼠标悬停事件用法解析
May 15 #Javascript
JavaScript enum枚举类型定义及使用方法
May 15 #Javascript
Vue如何基于es6导入外部js文件
May 15 #Javascript
JavaScript onclick事件使用方法详解
May 15 #Javascript
Vue使用富文本编辑器Vue-Quill-Editor(含图片自定义上传服务、清除复制粘贴样式等)
May 15 #Javascript
用Angular实现一个扫雷的游戏示例
May 15 #Javascript
Node.js API详解之 dns模块用法实例分析
May 15 #Javascript
You might like
PHP 登录记住密码实现思路
2013/05/07 PHP
PHP遍历某个目录下的所有文件和子文件夹的实现代码
2013/06/28 PHP
PHP检测字符串是否为UTF8编码的常用方法
2014/11/21 PHP
jQuery(1.3.2) 7行代码搞定跟随屏幕滚动的层
2009/05/21 Javascript
jQuery 学习 几种常用方法
2009/06/11 Javascript
瀑布流布局代码一例
2014/04/11 Javascript
基于jQuery的JavaScript模版引擎JsRender使用指南
2014/12/29 Javascript
jQuery实现form表单基于ajax无刷新提交方法详解
2015/12/08 Javascript
js判断手机系统是android还是ios
2017/03/07 Javascript
关于在vue-cli中使用微信自动登录和分享的实例
2017/06/22 Javascript
AngularJS2 与 D3.js集成实现自定义可视化的方法
2017/12/01 Javascript
nodejs简单读写excel内容的方法示例
2018/03/16 NodeJs
微信小程序仿微信运动步数排行(交互)
2018/07/13 Javascript
从零开始搭建vue移动端项目到上线的步骤
2018/10/15 Javascript
记一次用vue做的活动页的方法步骤
2019/04/11 Javascript
微信小程序网络层封装的实现(promise, 登录锁)
2019/05/08 Javascript
使用vue cli4.x搭建vue项目的过程详解
2020/05/08 Javascript
解决vue2中使用elementUi打包报错的问题
2020/09/22 Javascript
[10:54]Team Spirit vs Navi
2018/06/07 DOTA
[38:39]完美世界DOTA2联赛循环赛 IO vs GXR BO2第二场 11.04
2020/11/05 DOTA
python读写二进制文件的方法
2015/05/09 Python
Python基于TCP实现会聊天的小机器人功能示例
2018/04/09 Python
python logging日志模块原理及操作解析
2019/10/12 Python
浅析python redis的连接及相关操作
2019/11/07 Python
用pytorch的nn.Module构造简单全链接层实例
2020/01/14 Python
django项目中新增app的2种实现方法
2020/04/01 Python
Tensorflow加载Vgg预训练模型操作
2020/05/26 Python
HTML5自定义视频播放器源码
2020/01/06 HTML / CSS
英国性感内衣和睡衣品牌:Bluebella
2018/01/26 全球购物
SKECHERS斯凯奇中国官网:来自美国的运动休闲品牌
2018/11/14 全球购物
应届生的求职推荐信范文
2013/11/30 职场文书
公司年会演讲稿范文
2014/01/11 职场文书
鼓励运动员的广播稿
2014/02/08 职场文书
医院合作协议书
2014/08/19 职场文书
免职证明样本
2014/10/23 职场文书
2015年国庆晚会主持词
2015/07/01 职场文书