Vue 2.0双向绑定原理的实现方法


Posted in Javascript onOctober 23, 2019

Object.defineProperty方法

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体实现过程的代码如下:

1、定义构造函数

function Vue(option){
 this.$el = document.querySelector(option.el); //获取挂载节点
 this.$data = option.data;
 this.$methods = option.methods;
 this.deps = {}; //所有订阅者集合 目标格式(一对多的关系):{msg: [订阅者1, 订阅者2, 订阅者3], info: [订阅者1, 订阅者2]}
 this.observer(this.$data); //调用观察者
 this.compile(this.$el); //调用指令解析器
}

2、定义指令解析器

Vue.prototype.compile = function (el) {
 let nodes = el.children; //获取挂载节点的子节点
 for (var i = 0; i < nodes.length; i++) {
 var node = nodes[i];
 if (node.children.length) {
 this.compile(node) //递归获取子节点
 }
 if (node.hasAttribute('l-model')) { //当子节点存在l-model指令
 let attrVal = node.getAttribute('l-model'); //获取属性值
 node.addEventListener('input', (() => {
 this.deps[attrVal].push(new Watcher(node, "value", this, attrVal)); //添加一个订阅者
 let thisNode = node;
 return () => {
  this.$data[attrVal] = thisNode.value //更新数据层的数据
 }
 })())
 }
 if (node.hasAttribute('l-html')) {
 let attrVal = node.getAttribute('l-html'); //获取属性值
 this.deps[attrVal].push(new Watcher(node, "innerHTML", this, attrVal)); //添加一个订阅者
 }
 if (node.innerHTML.match(/{{([^\{|\}]+)}}/)) {
 let attrVal = node.innerHTML.replace(/[{{|}}]/g, ''); //获取插值表达式内容
 this.deps[attrVal].push(new Watcher(node, "innerHTML", this, attrVal)); //添加一个订阅者
 }
 if (node.hasAttribute('l-on:click')) {
 let attrVal = node.getAttribute('l-on:click'); //获取事件触发的方法名
 node.addEventListener('click', this.$methods[attrVal].bind(this.$data)); //将this指向this.$data
 }
 }
}

3、定义观察者

Vue.prototype.observer = function(data){
 for(var key in data){
 (function(that){
 let val = data[key]; //每一个数据的属性值
 that.deps[key] = []; //初始化所有订阅者对象{msg: [订阅者], info: []}
 var watchers = that.deps[key];
 Object.defineProperty(data, key, { //数据劫持
 get: function(){
  return val;
 },
 set: function(newVal){ //设置值(说明有数据更新)
  if(val !== newVal){
  val = newVal;
  }
  // 通知订阅者
  watchers.forEach(watcher=>{
  watcher.update()
  })
 }
 })
 })(this)
 }
}

4、定义订阅者

function Watcher(el, attr, vm, attrVal) {
 this.el = el;
 this.attr = attr;
 this.vm = vm;
 this.val = attrVal;
 this.update(); //更新视图
}

5、更新视图

Watcher.prototype.update = function () {
 this.el[this.attr] = this.vm.$data[this.val]
}

以上代码定义在一个Vue.js文件中,在需要使用双向绑定的地方引入即可。

尝试使用一下:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Document</title>
 <script src="./vue.js"></script>
</head>
<body>
 <!--
 实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:
 1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
 2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
 3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
 4、mvvm入口函数,整合以上三者
 -->
 <div id="app">
 <input type="text" l-model="msg" >
 <p l-html="msg"></p>
 <input type="text" l-model="info" >
 <p l-html="info"></p>
 <button l-on:click="clickMe">点我</button>
 <p>{{msg}}</p>
 </div>

 <script>
 var vm = new Vue({
 el: "#app",
 data: {
 msg: "恭喜发财",
 info: "好好学习, 天天向上"
 },
 methods: {
 clickMe(){
  this.msg = "我爱敲代码";
 }
 }
 })
 </script>
</body>
</html>

更多教程点击《Vue.js前端组件学习教程》,欢迎大家学习阅读。

关于vue.js组件的教程,请大家点击专题vue.js组件学习教程进行学习。

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

Javascript 相关文章推荐
jquery拖拽排序简单实现方法(效果增强版)
Feb 16 Javascript
详解Angular.js的$q.defer()服务异步处理
Nov 06 Javascript
基于JavaScript实现随机颜色输入框
Dec 10 Javascript
Vue.js中用v-bind绑定class的注意事项
Dec 13 Javascript
在DWR中实现直接获取一个JAVA类的返回值的两种方法
Dec 25 Javascript
node.js的事件机制
Feb 08 Javascript
jquery将标签元素的高设为屏幕的百分比
Apr 19 jQuery
深入理解node.js http模块
Jan 24 Javascript
浅谈Vue 数据响应式原理
May 07 Javascript
微信小程序开发的基本流程步骤
Jan 31 Javascript
浅谈微信小程序列表埋点曝光指南
Oct 15 Javascript
VUE 动态组件的应用案例分析
Dec 02 Javascript
p5.js绘制旋转的正方形
Oct 23 #Javascript
p5.js实现简单货车运动动画
Oct 23 #Javascript
p5.js实现故宫橘猫赏秋图动画
Oct 23 #Javascript
vue父组件给子组件的组件传值provide inject的方法
Oct 23 #Javascript
p5.js临摹旋转爱心
Oct 23 #Javascript
JavaScript 作用域scope简单汇总
Oct 23 #Javascript
node.js使用fs读取文件出错的解决方案
Oct 23 #Javascript
You might like
php5中date()得出的时间为什么不是当前时间的解决方法
2008/06/30 PHP
基于PHP编程注意事项的小结
2013/04/27 PHP
php弹出对话框实现重定向代码
2014/01/23 PHP
Laravel 5框架学习之向视图传送数据(进阶篇)
2015/04/08 PHP
Laravel 错误提示本地化的实现
2019/10/22 PHP
laravel框架中视图的基本使用方法分析
2019/11/23 PHP
在IE,Firefox,Safari,Chrome,Opera浏览器上调试javascript
2008/12/02 Javascript
浏览器打开层自动缓慢展开收缩实例代码
2013/07/04 Javascript
js window.open弹出新的网页窗口
2014/01/16 Javascript
实例讲解JQuery中this和$(this)区别
2014/12/08 Javascript
js实现文本框中输入文字页面中div层同步获取文本框内容的方法
2015/03/03 Javascript
jQuery+css实现炫目的动态块漂移效果
2016/01/28 Javascript
JS与HTML结合使用marquee标签实现无缝滚动效果代码
2016/07/05 Javascript
jQuery stop()用法实例详解
2016/07/28 Javascript
JS中判断null的方法分析
2016/11/21 Javascript
JS库particles.js创建超炫背景粒子插件(附源码下载)
2017/09/13 Javascript
p5.js入门教程之图片加载
2018/03/20 Javascript
在Vue项目中使用d3.js的实例代码
2018/05/01 Javascript
React Native基础入门之初步使用Flexbox布局
2018/07/02 Javascript
js实现同一个页面,多个enter事件绑定的示例
2018/10/10 Javascript
用js编写留言板
2020/03/17 Javascript
vue中解决chrome浏览器自动播放音频和MP3语音打包到线上的实现方法
2020/10/09 Javascript
[06:53]2018DOTA2国际邀请赛寻真——为复仇而来的Newbee
2018/08/15 DOTA
[01:01:52]完美世界DOTA2联赛PWL S2 GXR vs Magma 第二场 11.25
2020/11/26 DOTA
Django实现从数据库中获取到的数据转换为dict
2020/03/27 Python
设置jupyter中DataFrame的显示限制方式
2020/04/12 Python
零基础学Python之前需要学c语言吗
2020/07/21 Python
关于PyCharm安装后修改路径名称使其可重新打开的问题
2020/10/20 Python
CSS教程:CSS3圆角属性
2009/04/02 HTML / CSS
工艺工程师岗位职责
2014/03/04 职场文书
安全生产责任书
2014/03/12 职场文书
住房租房协议书
2014/08/20 职场文书
交通违章检讨书
2014/09/21 职场文书
公司股份转让协议书范本
2015/01/28 职场文书
乔布斯辞职信(中英文对照)
2015/05/12 职场文书
《我的伯父鲁迅先生》教学反思
2016/02/16 职场文书