经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
浅谈vue原理(三)
来源:cnblogs  作者:java小新人  时间:2021/1/4 9:40:12  对本文有异议

  上一篇已经实现了发布订阅模式了,现在我们实现从model->view的数据绑定,也就是当data中的数据改变后,页面上的数据也要跟着变化;

1.发布订阅代码的实际应用

  我们思考一下,怎么把我们上一篇实现到的发布订阅模式用到我们的vue中去呢?

  (1)Watcher应该什么时机创建呢?

  之前说了一个{{xxx}}占位符就代表一个watcher,那么刚好在遍历虚拟节点树的时候会正则判断每个出现{{xxx}}的所在节点,此时创建watcher实例正合适;

 

 (2)Watcher构造函数中做了什么?

  在上图中可以看到Watcher中传入了三个参数,其中根据第一个和第二个参数就可以取到data中对应的{{user.name}}的值myVue[user][name],由于所有的属性都绑定了有get方法,所以在获取myVue[user][name]的时候就会触发get方法,所以我们可以在get方法中添加新的逻辑,就是将当前Watcher注册到Dep中一份;

(2.1)Watcher构造器(注意这里最后设置为null的这句,后面有用):

 

(2.2)get方法新增逻辑:

  

(3)那么当我们修改myVue.user.name=”hello“的时候会发生什么?

  由于之前的数据劫持和数据代理,所以此时会触发name这个属性的set方法,我们需要在这个set方法中添加逻辑,就是触发Dep中所有的Watcher的回调方法,就是(1)中回调函数;

 

(4)在(3)调用notify之后,每个watcher将会执行的行为?

 

(5)大概捋一下思路:

  (5.1)首先启动项目,就会进行初始化,在编译模板的阶段会把html标签中的所有{{}}占位符都找到,我们就在这个时候新建Watcher,并设置回调函数就是给每个{{}},以便于后续调用直接覆盖这个占位符

  (5.2)在Watcher创建的时候,会由于从data中取值的原因,会触发该属性的get方法,我们就把这个Watcher实例的指针丢到这个get方法中,实现注册到Dep的逻辑

  (5.3)在每个属性的set方法中加入了notify的机制,这是为了保证只要我们手动的将data数据修改之后,就会调用所有注册到Dep中的Watcher的回调函数

  (5.4)启动完毕

  (5.5)当我们修改data中的数据的时候,首先会触发set方法中的notify方法,直接调用所有的Watcher的update方法实现发布,在update方法中会获取data中修改的最新值(此时虽然也会触发这个属性的get方法,但是没有target属性,因为target属性只会在创建Watcher的时候才会赋值,创建完了之后就会设置为null,不会去重复注册同一个Watcher)

   (5.6)获取到了最新的值之后,传到(5.1)中的回调函数,就可以实现覆盖虚拟节点中的占位符{{}},实现页面的刷新,我们就能看到效果

 

2.代码

html:

  1. <body>
  2. <div id="app">
  3. <h1>呵呵:{{user.name}}</h1>
  4. </div>
  5.  
  6. <script src="./mvvm.js"></script>
  7. <script>
  8.  
  9. // 自定义的myVue实例
  10. let myVue = new MyVue({
  11. el: '#app',
  12. data: {
  13. user: { name: "小王" }
  14. }
  15. })
  16. </script>
  17. </body>

 

js:

  1. function MyVue (options = {}) {
  2. //第一步:首先就是将实例化的对象给拿到,得到data对象
  3. this.$options = options;
  4. this._data = this.$options.data;
  5. //第二步:数据劫持,将data对象中每一个属性都设置get/set方法
  6. observe(this._data);
  7. //第三步:数据代理,这里就是将_data的对象属性放到myVue实例中一份,实际的数据还是_data中的
  8. for (let key in this._data) {
  9. //这里的this代表当前myVue实例对象
  10. Object.defineProperty(this, key, {
  11. enumerable: true,
  12. get () {
  13. return this._data[key];
  14. },
  15. set (newVal) {
  16. this._data[key] = newVal;
  17. }
  18. })
  19. }
  20. //第四步:compile模板,需要将el属性和当前myVue实例
  21. compile(options.el, this)
  22. }
  23. function compile (el, vm) {
  24. return new Compile(el, vm);
  25. }
  26. function Compile (el, vm) {
  27. //将el代表的那个dom节点挂载到myVue实例中
  28. vm.$el = document.querySelector(el);
  29. //创建虚拟节点容器树
  30. let fragment = document.createDocumentFragment();
  31. //将el下所有的dom节点都放到容器树中,注意appendChild方法,这里是将将dom节点移动到容器树中啊,不是死循环!
  32. while (child = vm.$el.firstChild) {
  33. // console.log('count:' + vm.$el.childElementCount);
  34. fragment.appendChild(child)
  35. };
  36. //遍历虚拟节点中的所有节点,将真实数据填充覆盖这种占位符{{}}
  37. replace(fragment, vm);
  38. //将虚拟节点树中内容渲染到页面中
  39. vm.$el.appendChild(fragment);
  40. }
  41. function replace (n, vm) {
  42. //遍历容器树中所有的节点,解析出{{}}里面的内容,然后将数据覆盖到节点中去
  43. Array.from(n.childNodes).forEach(node => {
  44. console.log('nodeType:' + node.nodeType);
  45. let nodeText = node.textContent;
  46. let reg = /\{\{(.*)\}\}/;
  47. // 节点类型常用的有元素节点,属性节点和文本节点,值分别是1,2,3
  48. //一定要弄清楚这三种节点,比如<p id="123">hello</p>,这个p标签整个的就是元素节点,nodeType==1
  49. //id="123"可以看作是属性节点,nodeType==2
  50. //hello 表示文本节点,nodeType==3
  51. //因为占位符{{}}只在文本节点中,所以需要判断是否等于3
  52. if (node.nodeType === 3 && reg.test(nodeText)) {
  53. // RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串,以此类推,RegExp.$2。。。
  54. let arr = RegExp.$1.split(".");
  55. let val = vm;
  56. // 这个for循环就是取出这样的值:myVue[name][user]
  57. arr.forEach(i => {
  58. val = val[i];
  59. })
  60. // 创建Watcher,最主要的是传入的这个回调函数,会覆盖node节点中的占位符{{xxx}}
  61. new Watcher(vm, RegExp.$1, function (newVal) {
  62. node.textContent = nodeText.replace(reg, newVal);
  63. })
  64. // 把值覆盖到虚拟节点的占位符{{}}这里
  65. node.textContent = nodeText.replace(reg, val);
  66. }
  67. // 第一个遍历的节点是<div id="app">这一行后面的换行,nodeType等于3,但是没有占位符{{}},所以会进入到这里进行递归调用内部
  68. //的每一个节点,直到找到文本节点而且占位符{{}}
  69. if (node.childNodes) {
  70. replace(node, vm);
  71. }
  72. })
  73. }
  74. //数据劫持操作
  75. function observe (data) {
  76. // 如果data不是对象,就结束,不然递归调用会栈溢出的
  77. if (typeof data !== 'object') return;
  78. return new Observe(data);
  79. }
  80. function Observe (data) {
  81. let dep = new Dep();
  82. // 遍历data所有属性
  83. for (let key in data) {
  84. let val = data[key];
  85. //初始化的时候, data中就有复杂对象的时候,例如data: { message:{a:{b:1}}} ,就需要递归的遍历这个对象中每一个属性都添加get和set方法
  86. observe(val);
  87. Object.defineProperty(data, key, {
  88. enumerable: true,
  89. get () {
  90. // 订阅
  91. Dep.target && dep.addSub(Dep.target);
  92. return val;
  93. },
  94. set (newVal) {
  95. if (val === newVal) return;
  96. val = newVal;
  97. //当后续可能因为业务逻辑使得_data.message = {name: "小王"},设置对象类型的属性值,就需要递归的给对象中{name: "小王"}的每个属性也添加get和set方法
  98. //否则name是没有get/set方法的
  99. observe(val);
  100. dep.notify();
  101. }
  102. })
  103. }
  104. }
  105. // ===============================发布订阅===============================
  106. // 可以看做是公众号端
  107. function Dep () {
  108. // 存放每个用户的容器
  109. this.subs = [];
  110. }
  111. //对外提供的api之一,供用户订阅
  112. Dep.prototype.addSub = function (sub) {
  113. this.subs.push(sub);
  114. }
  115. // 对外提供的api之二,遍历每个用户,给每个用法发信息
  116. Dep.prototype.notify = function () {
  117. this.subs.forEach(sub => {
  118. sub.update();
  119. });
  120. }
  121. // 用户端
  122. function Watcher (vm, exp, fn) {
  123. // 这个可以看作是用户的标志;注意,这个fn一般是一个回调函数
  124. this.vm = vm;
  125. this.exp = exp;
  126. this.fn = fn;
  127. Dep.target = this;
  128. let val = vm;
  129. let arr = exp.split(".");
  130. arr.forEach(item => {
  131. val = val[item];
  132. })
  133. Dep.target = null;
  134. }
  135. // 用户端提供的对外api,让公众号端使用
  136. Watcher.prototype.update = function () {
  137. let val = this.vm;
  138. let arr = this.exp.split(".");
  139. arr.forEach(item => {
  140. val = val[item];
  141. })
  142. this.fn(val);
  143. }
View Code

   现阶段,当我们在控制台修改myVue.user.name="xxx"的时候,页面上也会跟着修改了

原文链接:http://www.cnblogs.com/wyq1995/p/14209560.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号