目标:

  • 完成数据的双向绑定
  • 完成v-bind
  • 完成v-on
  • 完成v-if

学习Proxy,掌握其原理

  1. let object = { num: 0, name: 'liming' };
  2. // 根据observed包装后,在get的时候获取值,set的时候修改值
  3. function reactive(obj) {
  4. let observed = new Proxy(obj, {
  5. get: function (obj, prop) {
  6. // obj代表所以对象 {name: "张三", age:12} props 代表获取谁
  7. console.log(obj, prop);
  8. return obj[prop];
  9. },
  10. set: function (obj, prop, value) {
  11. // obj代表所以对象 {name: "张三", age:12} props 代表替换谁 value 代表要替换的值
  12. console.log(obj, prop, value);
  13. obj[prop] = value;
  14. return true;
  15. },
  16. });
  17. return observed;
  18. }
  19. let result = reactive(object);
  20. console.log(result.num, '///');
  21. result.name = 'xiaohua';
  22. console.log(object, 'object');

实现简化版的RockVue

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <!-- <div id="app">
  10. {{message}}
  11. <input v-model="message" />
  12. </div> -->
  13. <!-- <div id="app-2">
  14. <span v-bind:title="message">
  15. 鼠标悬停几秒钟查看此处动态绑定的提示信息!
  16. </span>
  17. </div> -->
  18. <!-- <div id="app-5">
  19. <p>{{ message }}</p>
  20. <button v-on:click="reverseMessage">反转消息</button>
  21. </div> -->
  22. <div id="app-3">
  23. <p v-if="seen">现在你看到我了</p>
  24. <button v-on:click="reverseSeen">反转消息</button>
  25. </div>
  26. </body>
  27. <script type="module">
  28. import { RockVue as Vue } from './rockVue.js';
  29. new Vue({
  30. el: '#app-3',
  31. data: {
  32. seen: false,
  33. },
  34. methods: {
  35. reverseMessage: function () {
  36. this.message = this.message.split('').reverse().join('');
  37. },
  38. reverseSeen: function () {
  39. this.seen = !this.seen;
  40. // console.log(this.seen, ' this.seen');
  41. },
  42. },
  43. });
  44. </script>
  45. </html>
  1. export class RockVue {
  2. constructor(config) {
  3. this.template = document.querySelector(config.el);
  4. this.data = reactive(config.data);
  5. // 处理method的方法
  6. for (const name in config.methods) {
  7. this[name] = () => {
  8. config.methods[name].apply(this.data);
  9. };
  10. }
  11. this.traversal(this.template);
  12. }
  13. traversal(node) {
  14. if (node.nodeType === Node.TEXT_NODE) {
  15. if (node.textContent.trim().match(/^{{([\s\S]+)}}$/)) {
  16. let name = RegExp.$1.trim();
  17. effect(() => (node.textContent = this.data[name]));
  18. }
  19. }
  20. if (node.nodeType === Node.ELEMENT_NODE) {
  21. let attributes = node.attributes;
  22. console.log(attributes, 'attributes');
  23. for (let attribute of attributes) {
  24. // v-model
  25. if (attribute.name === 'v-model') {
  26. let name = attribute.value;
  27. effect(() => (node.value = this.data[name]));
  28. node.addEventListener('input', (event) => {
  29. this.data[name] = node.value;
  30. });
  31. }
  32. // v-bind title
  33. if (attribute.name.match(/^v\-bind:([\s\S]+)$/)) {
  34. let attrname = RegExp.$1;
  35. let name = attribute.value;
  36. effect(() => node.setAttribute(attrname, this.data[name]));
  37. }
  38. // v-on 事件处理
  39. if (attribute.name.match(/^v\-on:([\s\S]+)$/)) {
  40. let eventName = RegExp.$1;
  41. let fnname = attribute.value;
  42. node.addEventListener(eventName, this[fnname]);
  43. }
  44. // v-if 条件处理
  45. if (attribute.name === 'v-if') {
  46. let value = attribute.value;
  47. effect(() => {
  48. node.style.display = this.data[value] ? 'block' : 'none';
  49. });
  50. }
  51. }
  52. }
  53. if (node.childNodes && node.childNodes.length) {
  54. for (let child of node.childNodes) {
  55. this.traversal(child);
  56. }
  57. }
  58. }
  59. }
  60. let effects = new Map();
  61. let currentEffect = null;
  62. function effect(fn) {
  63. currentEffect = fn;
  64. fn();
  65. currentEffect = null;
  66. }
  67. function reactive(obj) {
  68. let observed = new Proxy(obj, {
  69. get: function (obj, prop) {
  70. // obj代表所以对象 {name: "张三", age:12} props 代表获取谁
  71. // console.log(obj, prop);
  72. if (currentEffect) {
  73. if (!effects.has(obj)) effects.set(obj, new Map());
  74. if (!effects.get(obj).has(prop))
  75. effects.get(obj).set(prop, new Array());
  76. effects.get(obj).get(prop).push(currentEffect);
  77. }
  78. return obj[prop];
  79. },
  80. set: function (obj, prop, value) {
  81. // obj代表所以对象 {name: "张三", age:12} props 代表替换谁 value 代表要替换的值
  82. // console.log(obj, prop, value);
  83. window.obj = obj;
  84. obj[prop] = value;
  85. if (effects.has(obj) && effects.get(obj).has(prop)) {
  86. for (let effect of effects.get(obj).get(prop)) {
  87. effect();
  88. }
  89. }
  90. return true;
  91. },
  92. });
  93. return observed;
  94. }
  95. // console.log(effects, 'effects');
  96. // window.effects = effects;

原理说明

主要通过Proxy对对象进行绑定监听处理,通过new Map对对象的属性操作进行处理,将要执行的函数匹配到存到对应的prop上面,通过每次的访问触发get方法,进行存方法的操作,通过修改触发set的方法,此时执行回调监听的函数,这样达到修改数据和视图的