Vue 3.0双向绑定原理的实现方法


Posted in Javascript onOctober 23, 2019

proxy方法

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过new Proxy()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

Vue 3.0与Vue 2.0的区别仅是数据劫持的方式由Object.defineProperty更改为Proxy代理,其他代码不变。可查看Vue 2.0双向绑定原理的实现

具体实现过程的代码如下:

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、定义观察者(区别在这一块代码)

Liu.prototype.observer = function (data) {
 const that = this;
 for(var key in data){
  that.deps[key] = []; //初始化所有订阅者对象{msg: [订阅者], info: []}
 }
 let handler = {
  get(target, property) {
   return target[property];
  },
  set(target, key, value) {
   let res = Reflect.set(target, key, value);
   var watchers = that.deps[key];
   watchers.map(item => {
    item.update();
   });
   return res;
  }
 }
 this.$data = new Proxy(data, handler);
}

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 相关文章推荐
用dom+xhtml+css制作的一个相册效果代码打包下载
Jan 24 Javascript
JavaScript中数组的排序、乱序和搜索实现代码
Nov 30 Javascript
jquery自定义下拉列表示例
Apr 25 Javascript
jquery实现简单的二级导航下拉菜单效果
Sep 07 Javascript
JavaScript模块规范之AMD规范和CMD规范
Oct 27 Javascript
jQuery树形控件zTree使用小结
Aug 02 Javascript
BootStrap下拉菜单和滚动监听插件实现代码
Sep 26 Javascript
node.js发送邮件email的方法详解
Jan 06 Javascript
详细介绍RxJS在Angular中的应用
Sep 23 Javascript
学习LayUI时自研的表单参数校验框架案例分析
Jul 29 Javascript
node获取客户端ip功能简单示例
Aug 24 Javascript
基于JavaScript实现随机点名器
Feb 25 Javascript
JavaScript判断数组类型的方法
Oct 23 #Javascript
Vue 2.0双向绑定原理的实现方法
Oct 23 #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
You might like
法压式咖啡之制作法
2021/03/03 冲泡冲煮
isset和empty的区别
2007/01/15 PHP
PHP中用hash实现的数组
2011/07/17 PHP
jQuery获取json后使用zy_tmpl生成下拉菜单
2015/03/27 PHP
php提交post数组参数实例分析
2015/12/17 PHP
深入分析PHP优化及注意事项
2016/07/04 PHP
swoole_process实现进程池的方法示例
2018/10/29 PHP
PHP解决高并发的优化方案实例
2020/12/10 PHP
JavaScript 学习笔记(十五)
2010/01/28 Javascript
jQuery 事件的命名空间简单了解
2013/11/22 Javascript
node.js中的http.response.writeHead方法使用说明
2014/12/14 Javascript
JavaScript中匿名函数用法实例
2015/03/23 Javascript
深入浅出 jQuery中的事件机制
2016/08/23 Javascript
PHP抓取HTTPS内容和错误处理的方法
2016/09/30 Javascript
微信小程序 绘图之饼图实现
2016/10/24 Javascript
基于ajax与msmq技术的消息推送功能实现代码
2016/12/26 Javascript
Vue 去除路径中的#号
2018/04/19 Javascript
Django+Vue跨域环境配置详解
2018/07/06 Javascript
浅谈ElementUI中switch回调函数change的参数问题
2018/08/24 Javascript
[54:15]DOTA2-DPC中国联赛 正赛 DLG vs Dragon BO3 第二场2月1日
2021/03/11 DOTA
python判断字符串编码的简单实现方法(使用chardet)
2016/07/01 Python
python基础_文件操作实现全文或单行替换的方法
2017/09/04 Python
django上传图片并生成缩略图方法示例
2017/12/11 Python
python并发和异步编程实例
2018/11/15 Python
python循环定时中断执行某一段程序的实例
2019/06/29 Python
对Pytorch中Tensor的各种池化操作解析
2020/01/03 Python
Jupyter Notebook输出矢量图实例
2020/04/14 Python
Python基于Faker假数据构造库
2020/11/30 Python
毕业生的自我鉴定该怎么写
2013/12/02 职场文书
个人自我评价范文
2014/02/05 职场文书
项目合作协议书
2014/04/16 职场文书
元旦联欢会策划方案
2014/06/11 职场文书
人力资源管理毕业求职信
2014/08/05 职场文书
呼啸山庄读书笔记
2015/06/29 职场文书
《神奇的鸟岛》教学反思
2016/02/22 职场文书
Python 用户输入和while循环的操作
2021/05/23 Python