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 相关文章推荐
JS 日期验证正则附asp日期格式化函数
Sep 11 Javascript
浅谈JavaScript中null和undefined
Jul 09 Javascript
JAVA四种基本排序方法实例总结
Jul 24 Javascript
jQuery旋转木马式幻灯片轮播特效
Dec 04 Javascript
在线引用最新jquery文件的实现方法
Aug 26 Javascript
功能强大的jquery.validate表单验证插件
Nov 07 Javascript
Bootstrap Modal遮罩弹出层代码分享
Nov 21 Javascript
如何使用bootstrap框架 bootstrap入门必看!
Apr 13 Javascript
12条写出高质量JS代码的方法
Jan 07 Javascript
详解vue中点击空白处隐藏div的实现(用指令实现)
Apr 19 Javascript
详解angularjs4部署文件过大解决过程
Dec 05 Javascript
何时使用Map来代替普通的JS对象
Apr 29 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读取MySQL数据代码
2008/06/05 PHP
PHP实现时间轴函数代码
2011/10/08 PHP
php5.3 goto函数介绍和示例
2014/03/21 PHP
通过Javascript读取本地Excel文件内容的代码示例
2014/04/08 Javascript
js中回调函数的学习笔记
2014/07/31 Javascript
JS实现霓虹灯文字效果的方法
2015/08/06 Javascript
15位和18位身份证JS校验的简单实例
2016/07/18 Javascript
Vue全家桶实践项目总结(推荐)
2017/11/04 Javascript
在小程序中集成redux/immutable/thunk第三方库的方法
2018/08/12 Javascript
JavaScript fetch接口案例解析
2018/08/30 Javascript
浅谈Vue.js 中的 v-on 事件指令的使用
2018/11/25 Javascript
使用VueRouter的addRoutes方法实现动态添加用户的权限路由
2019/06/03 Javascript
jsonp格式前端发送和后台接受写法的代码详解
2019/11/07 Javascript
Python完全新手教程
2007/02/08 Python
Python中shutil模块的常用文件操作函数用法示例
2016/07/05 Python
利用python求解物理学中的双弹簧质能系统详解
2017/09/29 Python
解决Python 爬虫URL中存在中文或特殊符号无法请求的问题
2018/05/11 Python
解决Django生产环境无法加载静态文件问题的解决
2019/04/23 Python
python创建属于自己的单词词库 便于背单词
2019/07/30 Python
python3实现mysql导出excel的方法
2019/07/31 Python
浅析pandas 数据结构中的DataFrame
2019/10/12 Python
python3利用Axes3D库画3D模型图
2020/03/25 Python
Python3使用 GitLab API 进行批量合并分支
2020/10/15 Python
python3爬虫中多线程的优势总结
2020/11/24 Python
7 For All Mankind官网:美国加州洛杉矶的高级牛仔服装品牌
2018/12/20 全球购物
我的网上商城创业计划书
2013/12/26 职场文书
中学生班主任评语
2014/01/30 职场文书
大学新生军训感言
2014/02/25 职场文书
财务人员担保书
2014/05/13 职场文书
乡镇党的群众路线教育实践活动总结报告
2014/10/30 职场文书
材料采购员岗位职责
2015/04/03 职场文书
2016创先争优活动党员公开承诺书
2016/03/24 职场文书
如何书写民事调解协议书?
2019/06/25 职场文书
商业计划书如何写?关键问题有哪些?
2019/07/11 职场文书
vue组件vue-esign实现电子签名
2022/04/21 Vue.js
详解Flutter自定义应用程序内键盘的实现方法
2022/06/14 Java/Android