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中使用Ajax赋值给全局变量失败异常的解决方法
Aug 18 Javascript
BootStrap和jQuery相结合实现可编辑表格
Apr 21 Javascript
BootStrap Table对前台页面表格的支持实例讲解
Dec 22 Javascript
JQ中$(window).load和$(document).ready区别与执行顺序
Mar 01 Javascript
使用jQuery ajaxupload插件实现无刷新上传文件
Apr 23 jQuery
详解Angular2中Input和Output用法及示例
May 21 Javascript
JS去掉字符串前后空格、阻止表单提交的实现代码
Jun 08 Javascript
angular中两种表单的区别(响应式和模板驱动表单)
Dec 06 Javascript
深入理解javascript prototype的相关知识
Sep 19 Javascript
javascript中可能用得到的全部的排序算法
Mar 05 Javascript
如何解决jQuery 和其他JS库的冲突
Jun 22 jQuery
vue如何使用rem适配
Feb 06 Vue.js
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
php 字符串函数收集
2010/03/29 PHP
PHP文件下载实例代码浅析
2016/08/17 PHP
Laravel日志用法详解
2016/10/09 PHP
php版微信小店API二次开发及使用示例
2016/11/12 PHP
关于Yii2框架跑脚本时内存泄漏问题的分析与解决
2019/12/01 PHP
写的htc的数据表格
2007/01/20 Javascript
CSS JavaScript 实现菜单功能 改进版
2008/12/09 Javascript
你未必知道的JavaScript和CSS交互的5种方法
2014/04/02 Javascript
浅析webapp框架AngularUI的demo
2014/12/21 Javascript
浅谈javascript中for in 和 for each in的区别
2015/04/23 Javascript
javascript实现拖动元素交换位置
2015/11/29 Javascript
jQuery实现获取绑定自定义事件元素的方法
2015/12/02 Javascript
BootStrap学习系列之Bootstrap Typeahead 组件实现百度下拉效果(续)
2016/07/07 Javascript
js获取地址栏中传递的参数(两种方法)
2017/02/08 Javascript
JavaScript创建对象的七种方式(推荐)
2017/06/26 Javascript
vue使用axios实现文件上传进度的实时更新详解
2017/12/20 Javascript
JS从非数组对象转数组的方法小结
2018/03/26 Javascript
解决Vue2.0中使用less给元素添加背景图片出现的问题
2018/09/03 Javascript
django使用xlwt导出excel文件实例代码
2018/02/06 Python
python 利用pandas将arff文件转csv文件的方法
2019/02/12 Python
Django框架下静态模板的继承操作示例
2019/11/08 Python
python 安装教程之Pycharm安装及配置字体主题,换行,自动更新
2020/03/13 Python
python 爬虫之selenium可视化爬虫的实现
2020/12/04 Python
Python读写Excel表格的方法
2021/03/02 Python
Nuts.com:优质散装,批发坚果、干果和巧克力等
2017/03/21 全球购物
卡骆驰英国官网:Crocs英国
2019/08/22 全球购物
Groupon荷兰官方网站:高达70%的折扣
2019/11/01 全球购物
写一个在SQL Server创建表的SQL语句
2012/03/10 面试题
中学校庆方案
2014/03/17 职场文书
小学综治宣传月活动总结
2014/07/02 职场文书
中华在我心中演讲稿
2014/09/13 职场文书
2014年社区宣传工作总结
2014/12/02 职场文书
导游词300字
2015/02/13 职场文书
高中物理教学反思
2016/02/19 职场文书
一篇文章带你搞懂Python类的相关知识
2021/05/20 Python
Vue实现动态查询规则生成组件
2021/05/27 Vue.js