请选择 进入手机版 | 继续访问电脑版
创作

一个简化版Vue助你理解Vue原理

Vue  / 只看大图  / 倒序浏览   © 著作权归作者本人所有

#楼主# 2020-6-18

跳转到指定楼层
vue.jpg

好多人对它的理解还是只是知道了大概原理,但是对具体的Vue双向绑定的实现很模糊,因此就出了这篇文章,供大家参考希望可以得到收获,以下是主要代码逻辑,先陈述一下这一过程都需要什么:

需要有一个接收Vue实例配置项的构造函数SimpleVue,给他加两个原型方法分别是observe()和compile(),再构造出一个订阅器watcher,给他加一个更新视图方法

  • observe():用来劫持并监听数据变化的数据监听器,有变化就会通知下文中的订阅器watcher
  • compile():节点DOM解析器,用来获取和解析每一个节点及其指令,根据初始化的模板数据来创建订阅器watcher
  • watcher():订阅器watcher,用来接收属性值的相关数据的变化通知,调用自身原型方法update从而更新视图

由于Vue就是一个MVVM的框架理念,所以就要通过Object.defineProperty()方法来劫持并监听所有属性值相关的数据,看看它是否变化,如有变化则通知订阅器watcher看是否需要视图更新,这一过程就是我们的数据监听器observe的工作任务,由于数据和订阅器是一对多的关系,所以通知订阅器的时候需要把数据对应的订阅器的集合都放在一个oWatcherObj对象中,接下来需要一个节点DOM解析器compile,主要用来迭代递归获取和解析每一个节点及其指令,根据初始化的模板数据来创建订阅器watcher,实例化watcher就会接到数据变化的通知,进而实现VM更新视图

  1. template:

  2. <div id="simpleVue">

  3. <button yf-on:click="copy">戳我</button>

  4. <div>

  5. <textarea yf-model="name"></textarea>

  6. <div yf-text="name"></div>

  7. </div>

  8. <hr>

  9. <button yf-on:click="show">显示/隐藏</button>

  10. <div yf-if="isShow">

  11. <input type="text" yf-model="webSite">

  12. <div yf-text="webSite"></div>

  13. </div>

  14. </div>

  15. SimpleVue构造:

  16. class SimpleVue { // 简化版Vue实例的构造 用来接收实例的配置项

  17. constructor(options) {

  18. this.$el = document.querySelector(options.el);

  19. this.$data = options.data;

  20. this.$methods = options.methods;

  21. this.oWatcherObj = {}; // 所有属性值相关的数据对应的订阅器的集合都放在该对象中

  22. this.observe(); // 调用数据监听器对属性值相关的数据进行劫持并监听

  23. this.compile(this.$el); // 对该DOM节点进行解析

  24. }

  25. observe() { // 数据监听器 用来劫持并监听属性值相关数据的变化 如有变化则通知订阅器watcher

  26. for (let key in this.$data) {

  27. let value = this.$data[key];

  28. this.oWatcherObj[key] = []; // 初始化该数据的订阅器 数据和订阅器的关系是一对多

  29. let oWatcherObj = this.oWatcherObj[key];

  30. Object.defineProperty(this.$data, key, { // 关键方法 可以修改对象身上的默认属性值的ES5方法 下面用到的是ES中两大属性中的访问器属性,有以下四种描述符对象

  31. configurable: false, // 该状态下的属性描述符不能被修改和删除

  32. enumerable: false, // 该状态下的属性描述符中的属性不可被枚举

  33. get() { // 属性值相关的数据读取函数

  34. return value;

  35. },

  36. set(newVal) { // 属性值相关的数据写入函数

  37. if (newVal !== value) {

  38. value = newVal;

  39. oWatcherObj.forEach((obj) => {

  40. obj.update(); // 通知和该数据相关的所有订阅器

  41. });

  42. }

  43. }

  44. });

  45. }

  46. }

  47. compile(el) { // 节点DOM解析器 用来获取和解析每一个节点及其指令 根据初始化的模板数据来创建订阅器watcher

  48. let nodes = el.children;

  49. for (let i = 0; i < nodes.length; i++) { // 迭代同级所有节点

  50. let node = nodes[i];

  51. if (node.children.length > 0) {

  52. this.compile(node); // 递归所有子节点

  53. }

  54. if (node.hasAttribute('yf-on:click')) { // 节点中如存在该指令则执行以下操作

  55. let eventAttrVal = node.getAttribute('yf-on:click');

  56. node.addEventListener('click', this.$methods[eventAttrVal].bind(this.$data)); // 绑定获取到的指令对应的数据所触发的方法

  57. }

  58. if (node.hasAttribute('yf-if')) {

  59. let ifAttrVal = node.getAttribute('yf-if');

  60. this.oWatcherObj[ifAttrVal].push(new Watcher(this, node, "", ifAttrVal)); // 给该指令对应的数据创建订阅器放在该数据对应的订阅器数组里

  61. }

  62. if (node.hasAttribute('yf-model')) {

  63. let modelAttrVal = node.getAttribute('yf-model');

  64. node.addEventListener('input', ((i) => { // 前方高能:此处有闭包请绕行!!! i的问题

  65. this.oWatcherObj[modelAttrVal].push(new Watcher(this, node, "value", modelAttrVal));

  66. return () => {

  67. this.$data[modelAttrVal] = nodes[i].value; // 将该指令所在节点的值扔给该指令的数据

  68. }

  69. })(i));

  70. }

  71. if (node.hasAttribute('yf-text')) {

  72. let textAttrVal = node.getAttribute('yf-text');

  73. this.oWatcherObj[textAttrVal].push(new Watcher(this, node, "innerText", textAttrVal));

  74. }

  75. }

  76. }

  77. }

  78. 订阅器构造:

  79. class Watcher { // 订阅器构造 用来接收属性值的相关数据的变化通知 从而更新视图

  80. constructor(...arg) {

  81. this.vm = arg[0];

  82. this.el = arg[1];

  83. this.attr = arg[2];

  84. this.val = arg[3];

  85. this.update(); // 初始化订阅器时更新一下视图

  86. }

  87. update() { // 将收到的新的数据更新在视图中从而实现真正的VM

  88. if (this.vm.$data[this.val] === true) {

  89. this.el.style.display = 'block';

  90. } else if (this.vm.$data[this.val] === false) {

  91. this.el.style.display = 'none';

  92. } else {

  93. this.el[this.attr] = this.vm.$data[this.val];

  94. }

  95. }

  96. }

  97. Shortcuts
复制代码

  • 戳我运行代码
  • Edit on GitHub
  • Try in JSFiddle

希望大家阅读完本文可以有所收获,因为能力有限,掌握的知识也是不够全面,欢迎大家提出来一起分享!谢谢O(∩_∩)O~

更多web前端进阶内容以及css、架构师干货,尽在WEB明教光明顶

转播转播 分享分享 分享淘帖 反对反对
回复

使用道具

成为第一个回答人

B Color Link Quote Code Smilies
站点地图|手机版|WEB明教光明顶 |湘ICP备19021820号-1
Powered by WEB明教  © 2017-2020 Starsoft.
返回顶部