image.png

初始化流程

image.png
在 new Vue() 之后。 Vue 会调用 _init 函数进行初始化,也就是这里的 init 过程,它会初始化生命周期、事件、 props、 methods、 data、 computed 与 watch 等。其中最重要的是通过 Object.defineProperty 设置 setter 与 getter 函数,用来实现「响应式」以及「依赖收集

编译

compile编译可以分成 parse、optimize 与 generate 三个阶段,最终需要得到 render function。
《剖析 Vue 内部运行机制》小册笔记 - 图3

parse

parse 会用正则等方式解析 template 模板中的指令、class、style等数据,形成AST。

optimize

optimize 的主要作用是标记 static 静态节点,这是 Vue 在编译过程中的一处优化,后面当 update 更新界面时,会有一个 patch 的过程, diff 算法会直接跳过静态节点,从而减少了比较的过程,优化了 patch 的性能。

generate

generate 是将 AST 转化成 render function 字符串的过程,得到结果是 render 的字符串以及 staticRenderFns 字符串。
在经历过 parse、optimize 与 generate 这三个阶段以后,组件中就会存在渲染 VNode 所需的 render function 了。

Vue 响应式原理

Object.defineProperty(obj, prop, descriptor)
descriptor的一些属性,简单介绍几个属性,具体可以参考 MDN 文档

  • enumerable,属性是否可枚举,默认 false。
  • configurable,属性是否可以被修改或者删除,默认 false。
  • get,获取属性的方法。
  • set,设置属性的方法。

    被观察者

    ```javascript class Vue { / Vue构造类 / constructor(options) {
    1. this._data = options.data;
    2. observer(this._data);
    } }

function observer (value) { if (!value || (typeof value !== ‘object’)) { return; } Object.keys(value).forEach((key) => { defineReactive(value, key, value[key]); }); } function defineReactive (obj, key, val) { Object.defineProperty(obj, key, { enumerable: true, / 属性可枚举 / configurable: true, / 属性可被修改或删除 / get: function reactiveGetter () { return val;
}, set: function reactiveSetter (newVal) { if (newVal === val) return; cb(newVal); } }); }

  1. <a name="UYf4U"></a>
  2. ### 依赖收集追踪原理
  3. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22789278/1662183818542-74986006-05d0-4b67-b21a-34354b4385cb.png#clientId=u0e2831a6-a5cc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=135&id=ua07d388a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=270&originWidth=610&originalType=binary&ratio=1&rotation=0&showTitle=false&size=55786&status=done&style=none&taskId=uee438887-4091-426e-9ff2-17fcc7f1142&title=&width=305)<br />简单理解: 页面上没用的数据发生改变时,页面无需重新渲染,多个组件共用一个数据A,数据A发生改变所有的组件都需要跟新
  4. ```javascript
  5. new Vue({
  6. template:
  7. `<div>
  8. <span>{{text1}}</span>
  9. <span>{{text2}}</span>
  10. <div>`,
  11. data: {
  12. text1: 'text1',
  13. text2: 'text2',
  14. text3: 'text3'
  15. }
  16. });

订阅者 Dep

首先我们来实现一个订阅者 Dep ,它的主要作用是用来存放 Watcher 观察者对象。

  1. class Dep {
  2. constructor () {
  3. /* 用来存放Watcher对象的数组 */
  4. this.subs = [];
  5. }
  6. /* 在subs中添加一个Watcher对象 */
  7. addSub (sub) {
  8. this.subs.push(sub);
  9. }
  10. /* 通知所有Watcher对象更新视图 */
  11. notify () {
  12. this.subs.forEach((sub) => {
  13. sub.update();
  14. })
  15. }
  16. }

Vue diff 比较

首先说一下 patch 的核心 diff 算法,我们用 diff 算法可以比对出两颗树的「差异」
diff 算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有 O(n),是一种相当高效的算法,如下图。
《剖析 Vue 内部运行机制》小册笔记 - 图4
这张图中的相同颜色的方块中的节点会进行比对,比对得到「差异」后将这些「差异」更新到视图上。
patch 的过程相当复杂,我们先用简单的代码来看一下。

  1. function patch (oldVnode, vnode, parentElm) {
  2. // 1. 旧节点不存在直接将新节点推到parentEle
  3. if (!oldVnode) {
  4. addVnodes(parentElm, null, vnode, 0, vnode.length - 1);
  5. } else if (!vnode) {
  6. // 2. 新节点不存在,删除父节点身上的旧节点
  7. removeVnodes(parentElm, oldVnode, 0, oldVnode.length - 1);
  8. } else {
  9. // 3. 相同则进行 diff 核心比较
  10. if (sameVnode(oldVNode, vnode)) {
  11. patchVnode(oldVNode, vnode);
  12. } else {
  13. removeVnodes(parentElm, oldVnode, 0, oldVnode.length - 1);
  14. addVnodes(parentElm, null, vnode, 0, vnode.length - 1);
  15. }
  16. }
  17. }

因为 patch 的主要功能是比对两个 VNode 节点,将「差异」更新到视图上,所以入参有新老两个 VNode 以及父节点的 element 。

如何判断两个节点是否相同

比较 key 标签类型,如果是 input 类型会比较 他们身上的 attrs 属性是否一致

  1. function sameVnode () {
  2. return (
  3. a.key === b.key &&
  4. a.tag === b.tag &&
  5. a.isComment === b.isComment &&
  6. (!!a.data) === (!!b.data) &&
  7. sameInputType(a, b)
  8. )
  9. }
  10. function sameInputType (a, b) {
  11. if (a.tag !== 'input') return true
  12. let i
  13. const typeA = (i = a.data) && (i = i.attrs) && i.type
  14. const typeB = (i = b.data) && (i = i.attrs) && i.type
  15. return typeA === typeB
  16. }

两个节点相同的时候,比较下层子节点

  1. function patchVnode (oldVnode, vnode) {
  2. if (oldVnode === vnode) {
  3. return;
  4. }
  5. // 静态节点跳过比较,直接设置新节点等于旧节点
  6. if (vnode.isStatic && oldVnode.isStatic && vnode.key === oldVnode.key) {
  7. vnode.elm = oldVnode.elm;
  8. vnode.componentInstance = oldVnode.componentInstance;
  9. return;
  10. }
  11. const elm = vnode.elm = oldVnode.elm;
  12. const oldCh = oldVnode.children;
  13. const ch = vnode.children;
  14. // 当新 VNode 节点是文本节点的时候,直接用 setTextContent 来设置 text
  15. if (vnode.text) {
  16. nodeOps.setTextContent(elm, vnode.text);
  17. } else {
  18. if (oldCh && ch && (oldCh !== ch)) {
  19. updateChildren(elm, oldCh, ch);
  20. } else if (ch) {
  21. if (oldVnode.text) nodeOps.setTextContent(elm, '');
  22. addVnodes(elm, null, ch, 0, ch.length - 1);
  23. } else if (oldCh) {
  24. removeVnodes(elm, oldCh, 0, oldCh.length - 1)
  25. } else if (oldVnode.text) {
  26. nodeOps.setTextContent(elm, '')
  27. }
  28. }
  29. }