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 相关文章推荐
将光标定位于输入框最右侧实现代码
Dec 04 Javascript
jQuery学习笔记(4)--Jquery中获取table中某列值的具体思路
Apr 10 Javascript
javascript获取url上某个参数的方法
Nov 08 Javascript
网站基于flash实现的Banner图切换效果代码
Oct 14 Javascript
JavaScript中的ArrayBuffer详细介绍
Dec 08 Javascript
在Javascript中处理字符串之big()方法的使用
Jun 08 Javascript
超全面的JavaScript开发规范(推荐)
Jan 21 Javascript
layui导航栏实现代码
May 19 Javascript
JavaScript设计模式之享元模式实例详解
Jan 17 Javascript
详解Vue中组件的缓存
Apr 20 Javascript
LayUI数据接口返回实体封装的例子
Sep 12 Javascript
基于JavaScript实现控制下拉列表
May 08 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调用数据库的存贮过程
2006/10/09 PHP
PHP添加MySQL数据记录代码
2008/06/07 PHP
PHP中3种生成XML文件方法的速度效率比较
2012/10/06 PHP
php单例模式实现方法分析
2015/03/14 PHP
Laravel5.1 框架表单验证操作实例详解
2020/01/07 PHP
javascript的trim,ltrim,rtrim自定义函数
2008/09/21 Javascript
jQuery 处理网页内容的实现代码
2010/02/15 Javascript
js获取事件源及触发该事件的对象
2013/10/24 Javascript
js操作输入框中选择内容兼容IE及其他主流浏览器
2014/04/22 Javascript
Jquery实现的简单轮播效果【附实例】
2016/04/19 Javascript
jQuery实现的鼠标经过时变宽的效果(附demo源码)
2016/04/28 Javascript
js实现浏览器倒计时跳转页面效果
2016/08/12 Javascript
vue实现表格增删改查效果的实例代码
2017/07/18 Javascript
详解react-native WebView 返回处理(非回调方法可解决)
2018/02/27 Javascript
对angularJs中2种自定义服务的实例讲解
2018/09/30 Javascript
vue+iview 兼容IE11浏览器的实现方法
2019/01/07 Javascript
angular 服务随记小结
2019/05/06 Javascript
Vue中通过属性绑定为元素绑定style行内样式的实例代码
2020/04/30 Javascript
再也不怕 JavaScript 报错了,怎么看怎么处理都在这儿
2020/12/09 Javascript
python套接字流重定向实例汇总
2016/03/03 Python
Python TestCase中的断言方法介绍
2019/05/02 Python
Pyqt5 实现跳转界面并关闭当前界面的方法
2019/06/19 Python
python几种常用功能实现代码实例
2019/12/25 Python
python可视化 matplotlib画图使用colorbar工具自定义颜色
2020/12/07 Python
基于CSS3实现的黑色个性导航菜单效果
2015/09/14 HTML / CSS
HTML5之SVG 2D入门12—SVG DOM及DOM操作介绍
2013/01/30 HTML / CSS
全球知名旅游社区巴西站点:TripAdvisor巴西
2016/07/21 全球购物
企业治理工作自我评价
2013/09/26 职场文书
人力资源经理的岗位职责范本
2014/02/28 职场文书
建筑专业毕业生自荐信
2014/05/25 职场文书
商场消防安全责任书
2014/07/29 职场文书
个人遵守党的政治纪律情况对照检查材料
2014/09/26 职场文书
班主任远程培训研修日志
2015/11/13 职场文书
火锅店的开业营销方案范本!
2019/07/05 职场文书
golang判断key是否在map中的代码
2021/04/24 Golang
MySQL数据库如何查看表占用空间大小
2022/06/10 MySQL