原理说明

  1. 通过建立虚拟dom树document.createDocumentFragment(),方法创建虚拟dom树。
  2. 一旦被监测的数据改变,会通过Object.defineProperty定义的数据拦截,截取到数据的变化。

截取到的数据变化,从而通过订阅——发布者模式,触发Watcher(观察者),从而改变虚拟dom的中的具体数据。

  1. 最后,通过更新虚拟dom的元素值,从而改变最后渲染dom树的值,完成双向绑定

Vue的模式是m-v-vm模式,即(model-view-modelView),通过modelView作为中间层(即vm的实例),进行
双向数据的绑定与变化。
而实现这种双向绑定的关键就在于:
Object.defineProperty订阅—发布者模式这两点。

最终效果

GIF2.gifHTML代码

  1. <div id="app">
  2. <input type="text" v-model="text">
  3. {{text}}
  4. </div>

JS代码

  1. /*
  2. *
  3. * 第一步:创建一个observe去监听vue的实例vm中data的所有属性
  4. 第二步:调用nodeToFragment()
  5. ①==劫持==id=‘app’的元素下的所有子节点。(后面对于DOM的操作都在这里进行操作,最后更新完成再更新到我们真正的DOM树上,避免对DOM的反复操作)
  6. ②在这里调用解析器compile
  7. 第三步:compile()
  8. ①遍历dom中节点是否有v指令属性,有的话就对视图进行==初始化==(如:把上面的text:‘hello world’初始化到<input type="text" v-model="text">的value中)
  9. ②为所有有v指令的节点==创建一个watcher==,并把对应的动态绑定属性值传过去
  10. compile中与Observer的联系:
  11. ③在初始化的过程中,就必定会调用到我们observe的get方法,因此==在Observe这里把watcher添加到订阅器Dep中==
  12. ④在compile中我们还会设置对事件的监听(如input事件),执行了对视图中的数据修改->反映到model数据中(调用observer的set方法)
  13. 第四步:Observer通知订阅器Dep,数据值已修改。Dep.notify()通知所有的watcher去更新视图
  14. */
  15. //监视者
  16. /**
  17. * [observe description]
  18. * @param {[type]} obj [vue的data]
  19. * @param {[type]} vm [vue实例]
  20. * @return {[type]} [description]
  21. */
  22. function observe(obj, vm) {
  23. Object.keys(obj).forEach(function(key) {
  24. defineReactive(vm, key, obj[key]);
  25. })
  26. };
  27. /**
  28. * [defineReactive description]
  29. * @param {[type]} obj [vue实例]
  30. * @param {[type]} key [属性名]
  31. * @param {[type]} val [属性值]
  32. * @return {[type]} [description]
  33. */
  34. function defineReactive(obj, key, val) {
  35. console.log('observe:defineReactive');
  36. var dep = new Dep();
  37. console.log(dep);
  38. //obj:vue实例vm(被操作的目标对象),key要定义or要修改的属性名
  39. Object.defineProperty(obj, key, {
  40. get: function() {
  41. console.log('gettttttttt!');
  42. console.log(Dep.target);
  43. if (Dep.target)
  44. dep.addSub(Dep.target)
  45. return val;
  46. },
  47. set: function(newVal) {
  48. console.log('set!!!!!!!!!!!!!!!!');
  49. if (newVal === val) return;
  50. val = newVal;
  51. console.log(val);
  52. dep.notify();
  53. }
  54. });
  55. };
  56. //解析器
  57. //node:劫持的dom, vm:vue实例
  58. function compile(node, vm) {
  59. console.log('compile-----start');
  60. var reg = /\{\{(.*)\}\}/;
  61. if (node.nodeType === 1) //元素,查找v-modal绑定的属性值
  62. {
  63. var attr = node.attributes;
  64. for (var i = 0; i < attr.length; i++) {
  65. if (attr[i].nodeName == 'v-model') {
  66. var name = attr[i].nodeValue; //v-model的属性名
  67. node.addEventListener('input', function(e) {
  68. console.log('检测到值改变:' + e.target.value);
  69. vm[name] = e.target.value; //给相应的 data 属性赋值,进而触发该属性的 set 方法
  70. });
  71. node.value = vm[name]; //把vue对应属性名的值赋给节点,触发get方法,vm.data[name]不会触发get方法,但是vm[name]会触发get方法!!
  72. node.removeAttribute('v-model'); //为什么要删除v-model?
  73. }
  74. }
  75. new Watcher(vm, node, name, 'input'); //通知watcher去修改?
  76. }
  77. if (node.nodeType === 3) {
  78. if (reg.test(node.nodeValue)) {
  79. var name = RegExp.$1; // 获取匹配到的字符串
  80. name = name.trim();
  81. new Watcher(vm, node, name, 'text');
  82. }
  83. }
  84. console.log('compile-----end');
  85. }
  86. //订阅器
  87. function Dep() {
  88. this.subs = [];
  89. console.log('dep');
  90. }
  91. Dep.prototype = {
  92. addSub: function(sub) {
  93. console.log('addToDep')
  94. this.subs.push(sub);
  95. },
  96. notify: function() {
  97. this.subs.forEach(function(sub) {
  98. sub.update(); //通知watch去update
  99. });
  100. }
  101. }
  102. //var vdom = nodeToFragment(document.getElementById('app'));
  103. //console.log(vdom);
  104. function nodeToFragment(node, vm) {
  105. console.log('nodeToFragment---start');
  106. var flag = document.createDocumentFragment();
  107. var child;
  108. //console.log(node.childNodes); // length : 5
  109. while (child = node.firstChild) {
  110. compile(child, vm); //把劫持的child给compile解析
  111. flag.appendChild(child);
  112. }
  113. //console.log(node.childNodes); // length : 0
  114. console.log('nodeToFragment------end')
  115. return flag;
  116. }
  117. /**
  118. * [Watcher 订阅者]
  119. * @param {[type]} vm [vue实例对象]
  120. * @param {[type]} node [含有v指令的节点]
  121. * @param {[type]} name [description]
  122. * @param {[type]} nodeType [description]
  123. */
  124. function Watcher(vm, node, name, nodeType) {
  125. console.log('watcher');
  126. console.log(this);
  127. //Dep target是Dep和watcher关联的唯一桥梁
  128. Dep.target = this;
  129. /**
  130. * 关于Dep.target: 是一个全局变量,保存了当前的watcher
  131. * wathcer构造函数中,为Dep.target赋值后会通过watcher.update()方法去调用observe.get()
  132. * 在observer.get()方法中就是根据Dep.target来把当前watcher加入到订阅器Dep中
  133. * 所以在把wathcer加入完Dep后,Dep.target就没有用了,所以在this.update()后面要把Dep.watcher设为null
  134. */
  135. this.name = name;
  136. this.node = node;
  137. this.vm = vm;
  138. this.nodeType = nodeType;
  139. this.update();
  140. Dep.target = null;
  141. }
  142. Watcher.prototype = {
  143. //更新视图上的值,{{ value }}
  144. update: function() {
  145. this.get();
  146. if (this.nodeType == 'text') {
  147. this.node.nodeValue = this.value;
  148. }
  149. if (this.nodeType == 'input') {
  150. this.node.value = this.value;
  151. }
  152. },
  153. // 获取 data 中的属性值
  154. get: function() {
  155. this.value = this.vm[this.name]; // 触发相应属性的 get
  156. }
  157. }
  158. function Vue(options) {
  159. console.log(this); //this是指向实例对象
  160. this.data = options.data;
  161. var data = this.data;
  162. observe(data, this); //第一步 监听vue的实例vm中data的所有属性
  163. var id = options.el;
  164. var dom = nodeToFragment(document.getElementById(id), this);//第二部 数据劫持做处理
  165. document.getElementById(id).appendChild(dom);//第三部 绑定到节点树
  166. }
  167. var vm = new Vue({
  168. el: 'app',
  169. data: {
  170. text: 'hello world'
  171. }
  172. })

DOME下载

vue原理解析代码.rar