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动态添加事件并可传参数示例代码
Oct 21 Javascript
实现checkbox全选、反选、取消JavaScript小脚本异常
Apr 10 Javascript
Javascript玩转继承(一)
May 08 Javascript
JS实现的Select三级下拉菜单代码
Aug 20 Javascript
js实现简洁的滑动门菜单(选项卡)效果代码
Sep 04 Javascript
简单谈谈node.js 版本控制 nvm和 n
Oct 15 Javascript
需灵活掌握的Bootstrap预定义排版类 你精通吗?
Jun 20 Javascript
JavaScript结合Bootstrap仿微信后台多图文界面管理
Jul 22 Javascript
Bootstrap文件上传组件之bootstrap fileinput
Nov 25 Javascript
JavaScript调试之console.log调试的一个小技巧分享
Aug 07 Javascript
vue实现nav导航栏的方法
Dec 13 Javascript
Vue 组件注册全解析
Dec 17 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中文件缓存转内存缓存的方法
2011/12/06 PHP
php中inlcude()性能对比详解
2012/09/16 PHP
php switch语句多个值匹配同一代码块的实现
2014/03/03 PHP
yii2 在控制器中验证请求参数的使用方法
2019/06/19 PHP
JS 退出系统并跳转到登录界面的实现代码
2013/06/29 Javascript
JS去除右边逗号的简单方法
2013/07/03 Javascript
extjs4 treepanel动态改变行高度示例
2013/12/17 Javascript
关于js数组去重的问题小结
2014/01/24 Javascript
《JavaScript DOM 编程艺术》读书笔记之JavaScript 图片库
2015/01/09 Javascript
js全选按钮的实现方法
2015/11/17 Javascript
Nodejs Stream 数据流使用手册
2016/04/17 NodeJs
jQuery使用serialize()表单序列化时出现中文乱码问题的解决办法
2016/07/27 Javascript
原生JS实现匀速图片轮播动画
2016/10/18 Javascript
JQuery.dataTables表格插件添加跳转到指定页
2017/06/09 jQuery
Vue 全局loading组件实例详解
2018/05/29 Javascript
element vue validate验证名称重复 输入框与后台重复验证 特殊字符 字符长度 及注意事项小结【实例代码】
2018/11/20 Javascript
vue element动态渲染、移除表单并添加验证的实现
2019/01/16 Javascript
详解React项目中碰到的IE问题
2019/03/14 Javascript
Python中type的构造函数参数含义说明
2015/06/21 Python
Python对文件和目录进行操作的方法(file对象/os/os.path/shutil 模块)
2017/05/08 Python
python2.7+selenium2实现淘宝滑块自动认证功能
2018/02/24 Python
python 读取目录下csv文件并绘制曲线v111的方法
2018/07/06 Python
python时间序列按频率生成日期的方法
2019/05/14 Python
python 实现视频 图像帧提取
2019/12/10 Python
recorder.js 基于Html5录音功能的实现
2020/05/26 HTML / CSS
联想西班牙官网:Lenovo西班牙
2018/08/28 全球购物
PHP中如何使用Cookie
2015/10/28 面试题
物业保安主管岗位职责
2013/12/25 职场文书
竞选村长演讲稿
2014/04/28 职场文书
情况说明书格式范文
2014/05/06 职场文书
课例研修方案
2014/05/31 职场文书
宾馆仓管员岗位职责
2014/07/27 职场文书
2016反腐倡廉警示教育心得体会
2016/01/13 职场文书
遇事可以测出您的见识与格局
2019/09/16 职场文书
CSS完成视差滚动效果
2021/04/27 HTML / CSS
Python爬虫之自动爬取某车之家各车销售数据
2021/06/02 Python