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 相关文章推荐
JavaScript Chart 插件整理
Jun 18 Javascript
Jquery读取URL参数小例子
Aug 30 Javascript
浅析用prototype定义自己的方法
Nov 14 Javascript
对table和ul实现js分页示例分享
Feb 24 Javascript
jQuery 过滤方法filter()选择具有特殊属性的元素
Jun 15 Javascript
JavaScript轻松创建级联函数的方法示例
Feb 10 Javascript
Angularjs根据json文件动态生成路由状态的实现方法
Apr 17 Javascript
利用ECharts.js画K线图的方法示例
Jan 10 Javascript
动态加载JavaScript文件的3种方式
May 05 Javascript
Layui 动态禁止select下拉的例子
Sep 03 Javascript
微信小程序如何通过用户授权获取手机号(getPhoneNumber)
Jan 21 Javascript
antd日期选择器禁止选择当天之前的时间操作
Oct 29 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
phpwind中的数据库操作类
2007/01/02 PHP
php实现的ping端口函数实例
2014/11/12 PHP
php获得文件大小和文件创建时间的方法
2015/03/13 PHP
让网页根据不同IE版本显示不同的内容
2009/02/08 Javascript
js AspxButton的客户端操作
2009/06/26 Javascript
jquery 双色表格实现代码
2009/12/08 Javascript
JavaScript 注册事件代码
2011/01/27 Javascript
Prototype源码浅析 String部分(四)之补充
2012/01/16 Javascript
js遍历td tr等html元素
2012/12/13 Javascript
javascript陷阱 一不小心你就中招了(字符运算)
2013/11/10 Javascript
jquery实现表单验证简单实例演示
2015/11/23 Javascript
详解javascript事件冒泡
2016/01/09 Javascript
教大家轻松制作Bootstrap漂亮表格(table)
2016/12/13 Javascript
Javascript ES6中数据类型Symbol的使用详解
2017/05/02 Javascript
JQuery 选择器、DOM节点操作练习实例
2017/09/28 jQuery
Three.js开发实现3D地图的实践过程总结
2017/11/20 Javascript
JavaScript实用代码小技巧
2018/08/23 Javascript
Vue2 监听属性改变watch的实例代码
2018/08/27 Javascript
[02:42]DOTA2城市挑战赛收官在即 四强之争风起云涌
2018/06/05 DOTA
[58:25]VP vs RNG 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
图文讲解选择排序算法的原理及在Python中的实现
2016/05/04 Python
Python实战小程序利用matplotlib模块画图代码分享
2017/12/09 Python
python实现指定文件夹下的指定文件移动到指定位置
2018/09/17 Python
深入理解Tensorflow中的masking和padding
2020/02/24 Python
如何更换python默认编辑器的背景色
2020/08/10 Python
手机端用rem+scss做适配的详解
2017/11/15 HTML / CSS
李宁官方网店:中国运动品牌
2017/11/02 全球购物
施华洛世奇巴西官网:SWAROVSKI巴西
2019/12/03 全球购物
将一个文本文件的内容按倒序打印出来
2015/01/05 面试题
.NET是怎么支持多种语言的
2015/02/24 面试题
自荐信格式写作方法有哪些呢
2013/11/20 职场文书
创业计划书怎样才能打动风投
2014/01/01 职场文书
军人违纪检讨书
2014/02/04 职场文书
厂办主管岗位职责范本
2014/02/28 职场文书
七一建党日演讲稿
2014/09/05 职场文书
Go语言的协程上下文的几个方法和用法
2022/04/11 Golang