https://juejin.im/post/5a83c7125188257a836c3508

#多种方式实现MVVM,本章先讲解基于defineProperty来实现,也就是类似于实现vue的mvvm功能。本系列有3种实现方式,defineProperty(VUE),脏检查(angular),原生js实现(发布订阅者模式)

github地址 查看源码 https://github.com/honeydlp/mvvm.git

#vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调。
先看原理图,再结合代码
带你手把手撸mvvm,再也不怕面试被问倒了 - 图1
代码贴图了,今天用掘金code 总是显示在一行,所以截图了,抱歉哈,代码在github上

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <input type="text" v-model="msg">
  10. {{msg}}
  11. </div>
  12. <script>
  13. // vue MVVM 的实现
  14. function MVVM (opts) {
  15. this.data = opts.data;
  16. this.el = opts.el;
  17. var data = this.data;
  18. var me = this;
  19. var el = document.querySelector(this.el);
  20. observer(data, this);
  21. var dom = nodeToFragment(el, this);
  22. el.appendChild(dom);
  23. }
  24. // 将el内元素 编译成fragment dom 片段
  25. function nodeToFragment (node, vm) {
  26. var frag = document.createDocumentFragment();
  27. var child;
  28. while (child = node.firstChild) {
  29. complie(child, vm);
  30. frag.append(child);
  31. }
  32. return frag;
  33. }
  34. // 解析指令,{{}}
  35. function complie (node, vm) {
  36. var reg = /\{\{(.*)\}\}/;
  37. if (node.nodeType == 1) { // 元素
  38. var attrs = node.attributes; // 拿到属性键值对
  39. for (var i = 0, len = attrs.length; i < len; i++) {
  40. if (attrs[i].nodeName == 'v-model') {
  41. var name = attrs[i].nodeValue;
  42. node.addEventListener('input', function (e) {
  43. vm[name] = e.target.value;
  44. })
  45. node.value = vm[name];
  46. node.removeAttribute('v-model');
  47. new Watch(vm, node, name, 'input'); // 添加个监听器
  48. }
  49. }
  50. }
  51. if (node.nodeType == 3) { // 元素或属性中的文本内容
  52. if (reg.test(node.nodeValue)) {
  53. var name = RegExp.$1;
  54. name = name.trim();
  55. node.nodeType = 'text';
  56. new Watch(vm, node, name, 'text');
  57. }
  58. }
  59. }
  60. // 监听器
  61. function Watch (vm, node, name, nodeType) {
  62. Dep.target = this;
  63. this.name = name;
  64. this.node = node;
  65. this.vm = vm;
  66. this.nodeType = nodeType;
  67. this.update();
  68. Dep.target = null;
  69. }
  70. Watch.prototype = {
  71. update: function () {
  72. this.get();
  73. if (this.nodeType == 'text') {
  74. this.node.nodeValue = this.value;
  75. }
  76. if (this.nodeType == 'input') {
  77. this.node.value = this.value;
  78. }
  79. },
  80. get: function () {
  81. this.value = this.vm[this.name];
  82. }
  83. };
  84. // observer
  85. function observer (data, vm) {
  86. Object.keys(data).forEach(function (key) {
  87. defineReactive(vm, key, data[key]);
  88. })
  89. }
  90. function defineReactive (vm, key, val) {
  91. var dep = new Dep();
  92. Object.defineProperty(vm, key, {
  93. get: function () {
  94. if (Dep.target) {
  95. dep.addSub(Dep.target);
  96. }
  97. return val;
  98. },
  99. set: function (newVal) {
  100. if (newVal == val) return;
  101. val = newVal;
  102. dep.notify();
  103. }
  104. })
  105. }
  106. function Dep () {
  107. this.subs = [];
  108. }
  109. Dep.prototype = {
  110. addSub: function (sub) {
  111. this.subs.push(sub);
  112. },
  113. notify: function () {
  114. this.subs.forEach(function (sub) {
  115. sub.update();
  116. })
  117. }
  118. }
  119. // MVVM调用
  120. var vm = new MVVM({
  121. el: '#app',
  122. data: {
  123. 'msg': 'hello word'
  124. }
  125. });
  126. </script>
  127. </body>
  128. </html>

参考文档:


white 改写

  1. function MVVM(opts) {
  2. this.el = opts.el;
  3. this.data = opts.data;
  4. observer(this.data, this);
  5. const el = document.querySelector(this.el);
  6. const dom = nodeToFragment(el, this);
  7. el.appendChild(dom);
  8. }
  9. function nodeToFragment(node, vm) {
  10. const frag = document.createDocumentFragment();
  11. let child;
  12. while (child = node.firstChild) {
  13. node.removeChild(child)
  14. compile(child, vm);
  15. frag.append(child)
  16. }
  17. return frag;
  18. }
  19. function compile(node, vm) {
  20. const reg = /\{\{(.*)\}\}/;
  21. if (node.nodeType === 1) {
  22. const attrs = node.attributes;
  23. for (let i = 0, len = attrs.length; i < len; i++) {
  24. if (attrs[i].nodeName === 'v-model') {
  25. const name = attrs[i].nodeValue;
  26. node.addEventListener('input', function (e) {
  27. vm[name] = e.target.value;
  28. });
  29. new Watcher(vm, node, name, 'input')
  30. node.removeAttribute('v-model');
  31. }
  32. }
  33. } else if (node.nodeType === 3) {
  34. if (reg.test(node.nodeValue)) {
  35. let name = RegExp.$1;
  36. name = name.trim();
  37. new Watcher(vm, node, name, 'text')
  38. }
  39. }
  40. }
  41. function observer(data, vm) {
  42. Object.keys(data).forEach(key => {
  43. defineReactive(vm, key, data[key]);
  44. })
  45. }
  46. function defineReactive(vm, key, value) {
  47. const dep = new Dep();
  48. Object.defineProperty(vm, key, {
  49. get() {
  50. if (Dep.target) {
  51. dep.addSub(Dep.target);
  52. }
  53. return value;
  54. },
  55. set(newValue) {
  56. if (newValue === value) return;
  57. value = newValue;
  58. dep.notify();
  59. },
  60. })
  61. }
  62. class Dep {
  63. subs = [];
  64. addSub(sub) {
  65. this.subs.push(sub);
  66. }
  67. notify() {
  68. this.subs.forEach(sub => {
  69. sub.update();
  70. })
  71. }
  72. }
  73. class Watcher {
  74. constructor(vm, node, name, nodeType) {
  75. Dep.target = this;
  76. this.name = name;
  77. this.node = node;
  78. this.vm = vm;
  79. this.nodeType = nodeType;
  80. this.update();
  81. Dep.target = null;
  82. }
  83. update() {
  84. if (this.nodeType === 'text') {
  85. this.node.nodeValue = this.vm[this.name];
  86. }
  87. if (this.nodeType === 'input') {
  88. this.node.value = this.vm[this.name];
  89. }
  90. }
  91. }