这是一篇简单的学习笔记。在学习一段时间Vue后,尝试实现一下Vue的数据绑定。

相关源码:https://github.com/buchuitoudegou/Data-Binding-demo

Vue的数据绑定机制利用了观察者设计模式,利用侦听器动态更新DOM元素中的值,以下是Vue在编译时绑定数据的过程。

绑定setter和getter

index.mjs是编译的入口。从SelfVue的构造函数我们可以看到,对于options中的data,我们会先对它进行一个observe的操作,即绑定getter和setter,这也是数据响应模式构造的开始。

// index.mjs
export function SelfVue(options) {this.data = options.data;observe(this.data);Object.keys(this.data).forEach((key) => {Object.defineProperty(this, key, {enumerable: true,configurable: true,get: () => {return this.data[key];},set: (newVal) => {this.data[key] = newVal;// console.log('set index');}});});new Compile(options.el, this);return this;
}
// Observable.mjs
export function observe(data) {if (!data || !(typeof(data) === 'object')) {return;}Object.keys(data).forEach((key) => {defineReactive(data, key, data[key]);});
}function defineReactive(data, key, val) {observe(val);const dep =  new Dep;Object.defineProperty(data, key, {enumerable: true,configurable: true,get: () => {if (Dep.target) {dep.addSub(Dep.target);}return val;},set: (newVal) => {// console.log(newVal);val = newVal;dep.notify();}});
}

defineReactive函数递归给data对象的每个属性(如果该属性也是对象,则递归执行)绑定getter和setter。

getter是这样定义的:如果这个属性被订阅了,则将它的侦听器加入到订阅数组中,最后返回这个属性的值。

// Dep.mjs
export class Dep {constructor() {this.subs = [];}static target = null;addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => sub.update());}
}

这里提到了两个概念侦听器(Watcher)和订阅(Dep)数组。侦听器接下来会提到,先简单介绍一下订阅的概念。订阅器(Dep)是一个实例,每个data中的对象(Object)都会有一个Dep实例(data自己也会有一个),它里面定义了订阅数组,存储了所有订阅某个属性的侦听器。订阅器有一个notify方法,用于通知订阅数组里面所有的侦听器,某个值发生了改变(并不会判断该侦听器是否侦听了这个值)。另外,订阅器类(Dep)有一个静态属性target,用于存储当前正在进行订阅操作的属性(相当于订阅器的办事窗口,而且只有一个窗口)。

介绍完订阅器的概念之后,继续之前的内容,setter的定义应该就可以猜到了:setter在调用时,除了更新这个属性的值,还会调用订阅器的notify。

上面的步骤是Vue的root实例在定义的时候做的第一步工作。由于这个时候还不知道哪些属性会绑定到DOM上,因此,所有订阅器的订阅数组都是空的。

编译DOM节点

// Compile.mjs
export class Compile {constructor(el, vm) {this.vm = vm;this.el = document.querySelector(el);this.fragment = null;this.init();}init() {if (this.el) {this.fragment = document.createDocumentFragment();let child = this.el.firstChild;while (child) {this.fragment.appendChild(child);child = this.el.firstChild;}[].slice.call(this.fragment.childNodes).forEach(node => {this.compileElement(node);});this.el.appendChild(this.fragment);}}compileElement(ele) {const reg = /{{(.*)}}/;if (ele.nodeType === 3) {if (reg.test(ele.nodeValue)) {let temp = RegExp.$1;temp = temp.trim();ele.nodeValue = getValue(this.vm, temp.split('.'));new Watcher(this.vm, temp.split('.'), (value) => {ele.nodeValue = value;});}} else if (ele.nodeType === 1) {const attr = ele.getAttribute('vmodel');if (attr) {ele.value = getValue(this.vm, attr.split('.'));new Watcher(this.vm, attr.split('.'), (value) => {ele.value = value;});ele.addEventListener('input', (e) => {this.vm[attr] = e.target.value;});}}}
}

这个步骤里,我们会知道哪些节点(HTMLElement或者文本节点)会和Vue的data有互动。给这些有互动的节点设置侦听器(Watcher)。接下来介绍一下Watcher。

Watcher设置

// Watcher.mjs
export function getValue(data, exp) {if (exp.length === 0) {return null;}let temp = data;exp.forEach((key) => {temp = temp[key];});return temp;
}export class Watcher {constructor(vm, exp, cb) {this.vm = vm;this.exp = exp;this.cb = cb;this.value = this.get();}update() {const value = getValue(this.vm.data, this.exp);const oldVal = this.value;if (value !== oldVal) {this.value = value;this.cb.call(this.vm, value, oldVal);}}get() {Dep.target = this;const value = getValue(this.vm.data, this.exp);Dep.target = null;return value;}
}

侦听器,顾名思义用于侦听某个属性的值是否发生了变化。我们在给某个属性构造侦听器的时候需要传入一个回调函数,这个回调函数用于更新对应DOM节点的值。至于哪个节点对应哪个属性值我们在编译的时候就可以得知。

Watcher在构造的时候,会强行调用这个属性值的getter。这是为什么呢?之前我们也提到过,getter中定义了,若某个值被订阅,则它的侦听器会加入到订阅数组中。因此,给某个值构造Watcher时,先让Dep的订阅窗口(Dep.target)指向这个Watcher,说明这个值需要被订阅,然后再调用这个值的getter,这样子Dep的订阅数组中就会把这个Watcher放进去。最后将订阅窗口(Dep.target)指向null,表示订阅完成,可以订阅下一个属性。

每个Watcher都会有一个update方法,用于调用之前提到过的回调函数。当Dep调用notify方法的时候,就会触发每个在订阅数组中的Watcher的update方法。update方法中确认自己的侦听的值是否发生了变化,若发生了变化则调用构造的时候传入的回调函数,更新DOM元素中的值。

当一个值发生变化时

当data中一个值发生了变化时,setter会调用对应Dep实例的notify函数,notify函数调用实例中存储的订阅数组中所有侦听器的update方法。update方法中,侦听器会判断自己侦听的值是否发生了变化。若发生了变化,则调用回调函数,更新DOM节点中的值。

参考

双向绑定

vue 的双向绑定原理及实现 - 前端 - 掘金

ele表格操作区根据数据_Vue数据绑定相关推荐

  1. Python表格操作之将数据写入到表格中

    近几年大数据的兴起,迎来了大数据的热潮时代 Python语言首当其冲,语言简单易学,封装了大量的算法库,是大数据中主流的语言 在我们用python处理数据的时候,经常需要将已经预处理好的数据写到表格中 ...

  2. Python的excel表格操作,数据提取分析

    自己编写了一些表格的操作方法,只需传入表格路径和表的位置,就能在根据自己使用的方法展示不同类型的数据如:字典,列表,字典里套列表,列表里套列表,查询最大行数,在指定的行数插入数据等,有疑问欢迎评论 c ...

  3. 1万条数据大概占多大空间_Vue - Table表格渲染上千数据优化

    这次项目经验会谈谈经常在项目中,针对成千上万数据渲染优化的不断探索来谈谈自己的体会,其目的就是保证用户浏览上万条数据的时候,UI要很流畅,确保用户操作过程中不会出现UI卡顿或者最糟糕的情况,直接浏览器 ...

  4. 【快速上手系列】保姆级Layuimini与SSM的联合使用教程(数据表格操作)

    [快速上手系列]保姆级Layuimini与SSM的联合使用教程(数据表格操作) 使用步骤 导入layuimini 下载layuimini文件 这个并不是直接运行的,需要用HBuilder导入 layu ...

  5. JS数组去重及表格操作行数据方法总结

    数组去重 一.利用ES6 Set去重(ES6中最常用) function unique (arr) {return Array.from(new Set(arr)) } var arr = [1,1, ...

  6. 自动化办公之excel教程(2):各种数据的输入,自动填充数据,表格操作小技巧

    一.各种类型数据的录入 1.数据输入 加粗样式选中单元格后,左上侧会出现字母和数字,如图中的C9,即可在文中输入数据. 2.货币输入 在某一单元格输入框里右键选中设置单元格格式. 3.数字输入 如果我 ...

  7. vue渲染大量数据如何优化_Vue - Table表格渲染上千数据优化

    Vue - Table表格渲染上千数据优化 此次项目经验会谈谈常常在项目中,针对成千上万数据渲染优化的不断探索来谈谈本身的体会,其目的就是保证用户浏览上万条数据的时候,UI要很流畅,确保用户操做过程当 ...

  8. pandas神器操作excel表格大全(数据分析数据预处理)

    使用pandas库操作excel,csv表格操作大全 [点我下载本文PDF电子版] 关注公众号"轻松学编程"了解更多,文末有公众号二维码,可以扫码关注哦. 前言 准备三份csv表格 ...

  9. Python3-excel文档操作(一):利用openpyxl库处理excel表格:excel表格的创建和数据的写入和读取excel

    1. 简介 在最初处理excel表格时,我用的是xlwt和wlrd,表格后缀是.xls.但是,在处理时发现,一个sheet的记录长度如果超过65535,就写入不进去了. 后来,就采用openpyxl库 ...

最新文章

  1. Linux内核探讨-- 第五章
  2. 【虚拟化】docker部署nginx
  3. Homepage Machine Learning Algorithm 浅谈深度学习中的激活函数 - The Activation Function in Deep Learning
  4. .Android项目导入时,出现的Could not write file 。。。。。。.classpath错误解决办法
  5. 解决报错:import sun.misc.BASE64Decoder无法找到
  6. ASP.NET Web API中的Controller
  7. Redis基数统计之HyperLogLog小内存大用处
  8. JSF 源代码赏析之FacesServlet
  9. 数值计算与优化(共轭梯度法和QR)
  10. UML与软件建模 第五次作业
  11. python df共有几行_从zero到hero,一款Python自然语言处理效率利器!
  12. 人类与AI结合的最佳形态是什么样?
  13. PHP字符编码绕过漏洞总结
  14. poj 1459-Power Network解题报告
  15. jQuery实现文字左右收缩效果示例
  16. Xiaojie雷达之路---雷达原理(二刷)匹配滤波器
  17. 苹果电脑Mac电脑使用心得M1芯片快捷键
  18. 【0514 更新中】CVPR 2019 论文汇总 按方向划分
  19. 双十一!如何下载各种热卖商品视频?
  20. 互联网的前世今生:Web 1.0、2.0、3.0

热门文章

  1. ffmpeg linux 命令,Linux命令行下转换媒体格式工具FFMPEG介绍
  2. mysql-proxy myrelay_myrelay
  3. java将jfif格式转换成ipg_win10系统将jfif格式转jpg的操作方法
  4. 封装、继承、多态的理解
  5. Codeforces 848C. Goodbye Souvenir
  6. OpenStack 存储服务 Cinder存储节点部署LVM (十四)
  7. ionic入门教程第十六课-在微信中使用ionic的解决方案(按需加载加强版)
  8. 如何通过apk获得包名及Activiy 名称
  9. sphinx全文检索功能 | windows下测试 (二)
  10. 【转帖】漫话C++0x(四) —- function, bind和lambda