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 相关文章推荐
javascript下过滤数组重复值的代码
Sep 10 Javascript
EasyUI实现第二层弹出框的方法
Mar 01 Javascript
BootStrap 轮播插件(carousel)支持左右手势滑动的方法(三种)
Jul 07 Javascript
微信小程序 Video API实例详解
Oct 02 Javascript
Javascript中call,apply,bind方法的详解与总结
Dec 12 Javascript
Angular2 路由问题修复详解
Mar 01 Javascript
JS实现的简单标签点击切换功能示例
Sep 21 Javascript
Angular开发实践之服务端渲染
Mar 29 Javascript
layDate插件设置开始和结束时间
Nov 15 Javascript
Node.js使用supervisor进行开发中调试的方法
Mar 26 Javascript
微信小程序云开发如何使用npm安装依赖
May 18 Javascript
javascript+css实现俄罗斯方块小游戏
Jun 28 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
php explode函数实例代码
2012/02/27 PHP
php使用fputcsv()函数csv文件读写数据的方法
2015/01/06 PHP
Zend Framework实现将session存储在memcache中的方法
2016/03/22 PHP
PHP中explode函数和split函数的区别小结
2016/08/24 PHP
使用swoole 定时器变更超时未支付订单状态的解决方案
2019/07/24 PHP
PHP与SQL语句写一句话木马总结
2019/10/11 PHP
js中的屏蔽的使用示例
2013/07/30 Javascript
使用CSS和jQuery模拟select并附提交后取得数据的代码
2013/10/18 Javascript
jQuery中[attribute]选择器用法实例
2014/12/31 Javascript
在JavaScript中访问字符串的子串
2015/07/07 Javascript
JavaScript使用RegExp进行正则匹配的方法
2015/07/11 Javascript
angular2使用简单介绍
2016/03/01 Javascript
AngularJS实践之使用NgModelController进行数据绑定
2016/10/08 Javascript
AngularJS表格添加序号的方法
2017/03/03 Javascript
20行js代码实现的贪吃蛇小游戏
2017/06/20 Javascript
js计算两个日期间的天数月的实例代码
2018/09/20 Javascript
Angular使用Restful的增删改
2018/12/28 Javascript
了解JavaScript表单操作和表单域
2019/05/27 Javascript
Python操作SQLite简明教程
2014/07/10 Python
详解Python3 中hasattr()、getattr()、setattr()、delattr()函数及示例代码数
2018/04/18 Python
python ChainMap 合并字典的实现步骤
2019/06/11 Python
python 实现查找文件并输出满足某一条件的数据项方法
2019/06/12 Python
Django ORM 常用字段与不常用字段汇总
2019/08/09 Python
Python 分发包中添加额外文件的方法
2019/08/16 Python
python Plotly绘图工具的简单使用
2020/03/03 Python
VIVOBAREFOOT赤脚鞋:让您的脚做自然的事情
2017/06/01 全球购物
极简鞋类,赤脚的感觉:Lems Shoes
2019/08/06 全球购物
全球领先的全景影像品牌:Insta360
2019/08/21 全球购物
机电一体化专业推荐信
2013/12/03 职场文书
毕业生欢送会主持词
2014/03/31 职场文书
中学生演讲稿
2014/04/26 职场文书
法学专业大学生实习自我鉴定
2014/10/05 职场文书
老人院义工活动感想
2015/08/07 职场文书
幽默口才训练经典句子(48句)
2019/08/19 职场文书
JVM之方法返回地址详解
2022/02/28 Java/Android
python对文档中元素删除,替换操作
2022/04/02 Python