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[js]获取url参数的代码
Oct 17 Javascript
jQuery动画效果-fadeIn fadeOut淡入浅出示例代码
Aug 28 Javascript
javascript中undefined与null的区别
Aug 16 Javascript
JS动态改变浏览器标题的方法
Apr 06 Javascript
Angularjs 创建可复用组件实例代码
Oct 09 Javascript
BootStrap 表单控件之单选按钮水平排列
May 23 Javascript
JavaScript递归函数解“汉诺塔”算法代码解析
Jul 05 Javascript
详解Vue.js iview实现树形权限表(可扩展表)
Sep 30 Javascript
jQuery使用bind动态绑定事件无效的处理方法
Dec 11 jQuery
详解VUE项目中安装和使用vant组件
Apr 28 Javascript
Vue使用localStorage存储数据的方法
May 27 Javascript
es6中let和const的使用方法详解
Feb 24 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
php递归获取目录内文件(包含子目录)封装类分享
2013/12/25 PHP
解决yii2左侧菜单子级无法高亮问题的方法
2016/05/08 PHP
PHP+Ajax实现的无刷新分页功能详解【附demo源码下载】
2017/07/03 PHP
JS类中定义原型方法的两种实现的区别
2007/03/08 Javascript
JavaScript 字符编码规则
2009/05/04 Javascript
使用jQuery全局事件ajaxStart为特定请求实现提示效果的代码
2010/12/30 Javascript
可兼容IE的获取及设置cookie的jquery.cookie函数方法
2013/09/02 Javascript
HTML页面登录时的JS验证方法
2014/05/28 Javascript
使用GruntJS构建Web程序之Tasks(任务)篇
2014/06/06 Javascript
js实现简单鼠标跟随效果的方法
2015/04/10 Javascript
jQuery实现的动态伸缩导航菜单实例
2015/05/07 Javascript
详解JavaScript ES6中的模板字符串
2015/07/28 Javascript
浅谈jQuery中ajaxPrefilter的应用
2016/08/01 Javascript
jQuery EasyUI 获取tabs的实例解析
2016/12/06 Javascript
前端 Vue.js 和 MVVM 详细介绍
2016/12/29 Javascript
快速掌握jQuery插件开发
2017/01/19 Javascript
js实现颜色阶梯渐变效果(Gradient算法)
2017/03/21 Javascript
JS使用贪心算法解决找零问题示例
2017/11/27 Javascript
JQuery样式操作、click事件以及索引值-选项卡应用示例
2019/05/14 jQuery
微信小程序实现搜索指定景点周边美食、酒店
2019/05/18 Javascript
vue实现数字滚动效果
2020/06/29 Javascript
vue3+typescript实现图片懒加载插件
2020/10/26 Javascript
[03:20]2015国际邀请赛全明星表演赛
2015/08/08 DOTA
Python爬取读者并制作成PDF
2015/03/10 Python
Python当中的array数组对象实例详解
2019/06/12 Python
Numpy对数组的操作:创建、变形(升降维等)、计算、取值、复制、分割、合并
2019/08/28 Python
python opencv 实现读取、显示、写入图像的方法
2020/06/08 Python
俄罗斯女装店:12storeez
2019/10/25 全球购物
八年级英语教学反思
2014/01/09 职场文书
函授生自我鉴定
2014/03/25 职场文书
企业管理标语
2014/06/10 职场文书
学生检讨书怎么写
2015/05/07 职场文书
一文搞懂redux在react中的初步用法
2021/06/09 Javascript
基于PyQT5制作一个桌面摸鱼工具
2022/02/15 Python
Python中的datetime包与time包包和模块详情
2022/02/28 Python
基于Apache Hudi在Google云构建数据湖平台的思路详解
2022/04/07 Servers