Vue源码分析阅读笔记

2/15/2021

#来源

#前置知识

  1. Flow-Vue2.x类型检查工具
  2. 类型检查的两种方式:类型推断和类型注释

Runtime Only 和Runtime + Compiler

  1. Runtime Only需要借助webpack把.vue文件编译成JavaScript
  2. Runtime + Compiler,没有做预编译时需要渲染时编译

#Virtual DOM

问题:频繁操作DOM会有一定的性能问题

用一个原生的JS对象去描述一个DOM节点比创建一个DOM代价小,Vue中用VNode描述Virtual DOM

映射到真实DOM还要经历VNode的create、diff、patch等过程

#数据驱动

目的:弄清楚模板和数据如何渲染成最终的 DOM。

所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作DOM,而是通过修改数据。

new Vue发生的事情

  1. 调用this._init方法,其中干了几件事:合并配置mergeOptions、初始化生命周期initLifecycle、初始化事件initEvents、初始化渲染initRender、初始化data、props、computed、watcher。

调用$mount方法

vm.$mount(vm.$options.el)
1
  1. $mount方法实现功能
  • el做了限制,不能挂载在body、html根节点(由于会对原有元素进行替换,缺少了body或者html标签造成结构体不完整,不符合规范)
  • 如果没有定义render方法,则会把el或者template字符串转成render
  • 调用原先原型上的$mount方法挂载,调用mountComponent

mountComponent

  • 先实例化一个渲染Watcher:初始化时执行回调函数、当vm实例中数据变化时执行回调函数。回调函数为:
updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
1
2
3
  1. _render()

执行createElement方法并返回vnode

  1. createElement

对_createElement的封装

  1. _createElement

函数参数:

  • context: VNode的上下文
  • tag: 标签
  • data: VNode数据
  • children: 当前VNode的子节点
  • normalizationType: 子节点规范的类型(编译生成还是用户手写)

children的规范化:normalizeChildren(children)(render函数编译生成)和simpleNormalizeChildren(children)

simpleNormalizeChildren: 由于functional component返回的是一个数组

normalizeChildren: render函数用户手写同时children只有一个节点,则创建createTextVNode;当编译slotv-for时调用normalizeArrayChildren

VNode创建

  • 判断tag是否为内置节点,如果是,则创建一个普通的VNode
  • 如果是已注册的组件名,则通过createComponent创建一个组件类型的VNode
  1. update
  • 首次渲染调用或数据更新调用
  1. 核心是调用vm.__patch__方法,浏览器端把VNode转成DOM使用了patch方法
// params: 旧的VNode节点(可以不存在或者一个DOM对象)、_render后返回的VNode节点、hydrating-是否是服务端渲染、给transition-group使用
function patch (oldVnode, vnode, hydrating, removeOnly) {
}
1
2
3
  1. patch方法调用了createPathFunctioncreatePathFunction最终返回一个patch方法
// backend中的两个属性值nodeOps和modules
// nodeOps表示对平台DOM的一些操作方法
// modules表示平台的一些模块
export function createPatchFunction (backend) {
}
1
2
3
4
5
  1. createElm的作用是通过虚拟节点创建真实的DOM并插入到它的父节点中,该函数中最重要的三步是createChildreninvokeCreateHooksinsert
function createElm (
  vnode,
  insertedVnodeQueue,
  parentElm,
  refElm,
  nested,
  ownerArray,
  index
) {
  // ...
   vnode.elm = vnode.ns
      ? nodeOps.createElementNS(vnode.ns, tag)
      : nodeOps.createElement(tag, vnode)

  // ...
  // 创建子元素
  createChildren(vnode, children, insertedVnodeQueue)

  // ...
  // 执行所有的create的钩子并把`vnode`push到insertedVnodeQueue
  invokeCreateHooks(vnode, insertedVnodeQueue)

  // ...
  //
  insert(parentElm, vnode.elm, refElm)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

#总结

new Vue过程

#组件化

import Vue from 'vue'
import App from './App.vue'

var app = new Vue({
  el: '#app',
  // 这里的 h 是 createElement 方法
  render: h => h(App)
})
1
2
3
4
5
6
7
8

在之前的createElement调用_createElement方法,如果是一个组件,则会通过createComponent方法创建一个VNode

vnode = createComponent(tag, data, context, children)
1

createComponent中包含三个关键步骤:

  1. 构造子类构造函数
  2. 安装组件钩子函数
installComponentHooks(data)
1
  1. 实例化VNode
_createElement() -> vnode = createComponent(tag, data, context, children) -> 1. Ctor = baseCtor.extend(Ctor) -> Vue.extend -> Sub(Vue子类) 2. installComponentHooks(data) -> mergeHook 3. new VNode
1

#patch

#computed原理

源码:

function initComputed (vm, computed) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null);
  // computed properties are just getters during SSR
  const isSSR = isServerRendering();

  for (const key in computed) {
    const userDef = computed[key];
    const getter = typeof userDef === 'function' ? userDef : userDef.get;
    if (getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      );
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    } else {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm);
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm);
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Last Updated:5/25/2024, 2:23:06 AM