vue的工作机制

image.png

初始化

在 new Vue() 时会调用_init()进行初始化,会初始化各种实例方法、全局方法、执行一些生命周期、初始化
props、data等状态。其中最重要的是data的「响应化」处理。 初始化之后调用 $mount 挂载组件,主要执行编译和首次更新

编译

编译模块分为三个阶段
1. parse:使用正则解析template中的vue的指令(v-xxx) 变量等等 形成抽象语法树AST
2. optimize:标记一些静态节点,用作后面的性能优化,在diff的时候直接略过
3. generate:把第一部生成的AST 转化为渲染函数 render function

更新

数据修改触发setter,然后监听器会通知进行修改,通过对比新旧vdom树,得到最小修改,就是 patch ,然后只 需要把这些差异修改即可

实现kvue

image.png

kvue源码

  1. // new KVue({
  2. // data: {
  3. // msg: 'hello'
  4. // }
  5. // })
  6. class KVue {
  7. constructor(options) {
  8. this.$options = options;
  9. this.$data = options.data;
  10. // 响应化
  11. this.observe(this.$data);
  12. // 测试代码
  13. // new Watcher(this, 'test');
  14. // this.test;
  15. // 创建编译器
  16. new Compile(options.el, this);
  17. if (options.created) {
  18. options.created.call(this);
  19. }
  20. }
  21. // 递归遍历,使传递进来的对象响应化
  22. observe(value) {
  23. if (!value || typeof value !== "object") {
  24. return;
  25. }
  26. // 遍历
  27. Object.keys(value).forEach(key => {
  28. // 对key做响应式处理
  29. this.defineReactive(value, key, value[key]);
  30. this.proxyData(key);
  31. });
  32. }
  33. // 在vue根上定义属性代理data中的数据
  34. proxyData(key) {
  35. Object.defineProperty(this, key, {
  36. get() {
  37. return this.$data[key];
  38. },
  39. set(newVal) {
  40. this.$data[key] = newVal;
  41. }
  42. });
  43. }
  44. //
  45. defineReactive(obj, key, val) {
  46. // 递归
  47. this.observe(val);
  48. // 创建Dep实例:Dep和key一对一对应
  49. const dep = new Dep();
  50. // 给obj定义属性
  51. Object.defineProperty(obj, key, {
  52. get() {
  53. // 将Dep.target指向的Watcher实例加入到Dep中
  54. Dep.target && dep.addDep(Dep.target);
  55. return val;
  56. },
  57. set(newVal) {
  58. if (newVal !== val) {
  59. val = newVal;
  60. dep.notify();
  61. }
  62. }
  63. });
  64. }
  65. }
  66. // Dep:管理若干watcher实例,它和key一对一关系
  67. class Dep {
  68. constructor() {
  69. this.deps = [];
  70. }
  71. addDep(watcher) {
  72. this.deps.push(watcher);
  73. }
  74. notify() {
  75. this.deps.forEach(watcher => watcher.update());
  76. }
  77. }
  78. // 保存ui中依赖,实现update函数可以更新之
  79. class Watcher {
  80. constructor(vm, key, cb) {
  81. this.vm = vm;
  82. this.key = key;
  83. this.cb = cb;
  84. // 将当前实例指向Dep.target
  85. Dep.target = this;
  86. this.vm[this.key];// 读一次key触发getter
  87. Dep.target = null;
  88. }
  89. update() {
  90. this.cb.call(this.vm, this.vm[this.key])
  91. // console.log(`${this.key}属性更新了`);
  92. }
  93. }

compile.js

  1. // 遍历模板,将里面的插值表达式处理
  2. // 另外如果发现k-xx, @xx做特别处理
  3. class Compile {
  4. constructor(el, vm) {
  5. this.$vm = vm;
  6. this.$el = document.querySelector(el);
  7. if (this.$el) {
  8. // 1.$el中的内容搬家到一个fragment,提高操作效率
  9. this.$fragment = this.node2Fragment(this.$el);
  10. // console.log(this.$fragment);
  11. // 2.编译fragment
  12. this.compile(this.$fragment);
  13. // console.log(this.$fragment);
  14. // 3.将编译结果追加至宿主中
  15. this.$el.appendChild(this.$fragment);
  16. }
  17. }
  18. // 遍历el,把里面内容搬到新创建fragment中
  19. node2Fragment(el) {
  20. const fragment = document.createDocumentFragment();
  21. let child;
  22. while ((child = el.firstChild)) {
  23. // 由于appenChild是移动操作
  24. fragment.appendChild(child);
  25. }
  26. return fragment;
  27. }
  28. // 把动态值替换,把指令和事件做处理
  29. compile(el) {
  30. // 遍历el
  31. const childNodes = el.childNodes;
  32. Array.from(childNodes).forEach(node => {
  33. if (this.isElement(node)) {
  34. // console.log("编译元素:" + node.nodeName);
  35. // 如果是元素节点,我们要处理指令k-xx,事件@xx
  36. this.compileElement(node);
  37. } else if (this.isInterpolation(node)) {
  38. // console.log("编译文本:" + node.textContent);
  39. this.compileText(node);
  40. }
  41. // 递归子元素
  42. if (node.childNodes && node.childNodes.length > 0) {
  43. this.compile(node);
  44. }
  45. });
  46. }
  47. isElement(node) {
  48. return node.nodeType === 1;
  49. }
  50. // 插值表达式判断
  51. isInterpolation(node) {
  52. // 需要满足{{xx}}
  53. return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
  54. }
  55. compileElement(node) {
  56. // 查看node的特性中是否有k-xx,@xx
  57. const nodeAttrs = node.attributes;
  58. Array.from(nodeAttrs).forEach(attr => {
  59. // 获取属性名称和值 k-text="abc"
  60. const attrName = attr.name; // k-text
  61. const exp = attr.value; // abc
  62. // 指令:k-xx
  63. if (attrName.indexOf("k-") === 0) {
  64. const dir = attrName.substring(2); // text
  65. // 执行指令
  66. this[dir] && this[dir](node, this.$vm, exp);
  67. } else if(attrName.indexOf('@') === 0) {
  68. // 事件 @click="handlClick"
  69. const eventName = attrName.substring(1); // click
  70. this.eventHandler(node, this.$vm, exp, eventName);
  71. }
  72. });
  73. }
  74. text(node, vm, exp) {
  75. this.update(node, vm, exp, "text");
  76. }
  77. // 双向数据绑定
  78. model(node, vm, exp) {
  79. // update是数据变了改界面
  80. this.update(node, vm, exp, "model");
  81. // 界面变了改数值
  82. node.addEventListener("input", e => {
  83. vm[exp] = e.target.value;
  84. });
  85. }
  86. modelUpdator(node, value) {
  87. node.value = value;
  88. }
  89. html(node, vm, exp) {
  90. this.update(node, vm, exp, "html");
  91. }
  92. htmlUpdator(node, value) {
  93. node.innerHTML = value;
  94. }
  95. eventHandler(node, vm, exp, eventName){
  96. // 获取回调函数
  97. const fn = vm.$options.methods && vm.$options.methods[exp];
  98. if(eventName && fn) {
  99. node.addEventListener(eventName, fn.bind(vm))
  100. }
  101. }
  102. // 把插值表达式替换为实际内容
  103. compileText(node) {
  104. // {{xxx}}
  105. // RegExp.$1是匹配分组部分
  106. // console.log(RegExp.$1);
  107. const exp = RegExp.$1;
  108. this.update(node, this.$vm, exp, "text");
  109. }
  110. // 编写update函数,它可复用
  111. // exp是表达式, dir是具体操作:text,html,model
  112. update(node, vm, exp, dir) {
  113. const fn = this[dir + "Updator"];
  114. fn && fn(node, vm[exp]);
  115. // 创建Watcher
  116. // new Vue({
  117. // data: {
  118. // xxx: 'bla'
  119. // }
  120. // })
  121. // exp就是xxx
  122. new Watcher(vm, exp, function() {
  123. fn && fn(node, vm[exp]);
  124. });
  125. }
  126. textUpdator(node, value) {
  127. node.textContent = value;
  128. }
  129. }