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 相关文章推荐
Javascript代码混淆综合解决方案-Javascript在线混淆器
Dec 18 Javascript
javascript 打开页面window.location和window.open的区别
Mar 17 Javascript
innerHTML 和 getElementsByName 在IE下面的bug 的解决
Apr 09 Javascript
使用Javascript接收get传递的值的代码
Nov 30 Javascript
js动态为代码着色显示行号
May 29 Javascript
angularJS 入门基础
Feb 09 Javascript
jQuery中DOM节点的删除方法总结(超全面)
Jan 22 Javascript
jQuery插件FusionCharts实现的2D饼状图效果【附demo源码下载】
Mar 03 Javascript
JavaScript 字符串数字左补位,右补位,取固定长度,截位扩展函数代码
Mar 25 Javascript
在js中做数字字符串补0(js补零)
Mar 25 Javascript
解决浏览器会自动填充密码的问题
Apr 28 Javascript
node.js 核心http模块,起一个服务器,返回一个页面的实例
Sep 11 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
关于JSON以及JSON在PHP中的应用技巧
2013/11/27 PHP
php如何解决无法上传大于8M的文件问题
2014/03/10 PHP
php 二维数组时间排序实现代码
2016/11/19 PHP
Zend Framework数据库操作方法实例总结
2016/12/11 PHP
php 调用ffmpeg获取视频信息的简单实现
2017/04/03 PHP
PHP 的Opcache加速的使用方法
2017/12/29 PHP
javascript中最常用的继承模式 组合继承
2010/08/12 Javascript
Js 时间间隔计算的函数(间隔天数)
2011/11/15 Javascript
ExtJS DOM元素操作经验分享
2013/08/28 Javascript
jquery动态加载js/css文件方法(自写小函数)
2014/10/11 Javascript
jQuery实现点击小图显示大图代码分享
2015/08/25 Javascript
基于javascript代码实现通过点击图片显示原图片
2015/11/29 Javascript
微信小程序 wx:key详细介绍
2016/10/28 Javascript
浅谈Vue.js
2017/03/02 Javascript
详解vue父子组件间传值(props)
2017/06/29 Javascript
vue中七牛插件使用的实例代码
2017/07/28 Javascript
微信小程序网络封装(简单高效)
2018/08/06 Javascript
python根据经纬度计算距离示例
2014/02/16 Python
python实现搜索文本文件内容脚本
2018/06/22 Python
wxPython的安装与使用教程
2018/08/31 Python
详解python使用pip安装第三方库(工具包)速度慢、超时、失败的解决方案
2018/12/02 Python
详解Python3之数据指纹MD5校验与对比
2019/06/11 Python
python元组的概念知识点
2019/11/19 Python
Python request使用方法及问题总结
2020/04/26 Python
scrapy在python爬虫中搭建出错的解决方法
2020/11/22 Python
css3和jquery实现的可折叠导航菜单适合放在手机网页的导航菜单
2014/09/02 HTML / CSS
eDreams加拿大:廉价航班、酒店和度假
2019/03/29 全球购物
印度第一网上礼品店:IGP.com
2020/02/06 全球购物
adidas马来西亚官网:adidas MY
2020/09/12 全球购物
软件测试工程师结构化面试题库
2016/11/23 面试题
路政管理专业推荐信
2013/11/11 职场文书
离婚财产分隔协议书
2014/10/23 职场文书
被告答辩状范文
2015/05/22 职场文书
Redis源码阅读:Redis字符串SDS详解
2021/07/15 Redis
详解JavaScript中Arguments对象用途
2021/08/30 Javascript
Linux中文件的基本属性介绍
2022/06/01 Servers