目录

Vue的初始化渲染过程简介

首先进入到src/core/instance/index.js,可以看到定义了一个Vue构造函数,内容很简单,如果不是生产环境并且不是通过new关键字创建对象的话,就在控制台打印一个warn

1
2
3
4
5
6
7
8
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

后面调用了几个函数,用来在Vue对象上创建各种属性或者方法

1
2
3
4
5
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

1.initMixin

初始化方法,首先为当前vm设置一个_uid,然后添加属性_isVue,其目的在于监听数据变化时过滤vm_isComponent是内部创建子组件时才会添加为true的属性,然后走到else分支,调用resolveConstructorOptions会获取构造器父级的options,然后调用mergeOptions合并父级的options以及本身传入的options,最后生成的options包含components、directives等属性

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

2.initLifecycle

这个方法首先会设置vm的一个父子节点、根节点信息,然后会定义一些生命周期相关的属性,比如_isMounted、_isDestroyed、_isBeingDestroyed

 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
export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

3.initEvents

通过名字可以看出是一个初始化事件相关的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
export function initEvents (vm: Component) {
  // events表示父组件绑定在当前组件上的事件
  vm._events = Object.create(null)
  // 属性表示父组件是否通过"@hook:"把钩子函数绑定在当前组件上
  vm._hasHookEvent = false
  // init parent attached events
  // 同样是来表示父组件绑定在当前组件上的事件
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

如果listeners有值,则调用updateComponentListeners这个方法,其内部调用了updateListeners,其中的add方法会调用vm.$onvm.$on会监听当前实例上的自定义事件,remove会调用vm.$off,移出这个事件监听

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function add (event, fn) {
  target.$on(event, fn)
}

function remove (event, fn) {
  target.$off(event, fn)
}

export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  // 保存当前的vm引用
  target = vm
  // 传入listeners,父组件绑定在当前组件上的事件,oldListeners同理,不过在第一次初始化的时候为空
  // 其内部会遍历listeners调用add方法添加监听事件,同时移出oldListeners
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

4.initRender

主要是添加了一些虚拟dom、slot等相关的属性和方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
export function initRender (vm: Component) {
  // 表示虚拟dom节点
  vm._vnode = null 
  // 表示当前实例render得到的Vnode
  vm._staticTrees = null 
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode  tree
  const renderContext = parentVnode && parentVnode.context
  // 生成插槽内容
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  // 作用域插槽,此时为空
  vm.$scopedSlots = emptyObject
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  const parentData = parentVnode && parentVnode.data
	......
}

resolveSlots方法

 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
export function resolveSlots (
  children: ?Array<VNode>,
  context: ?Component
): { [key: string]: Array<VNode> } {
  if (!children || !children.length) {
    return {}
  }
  const slots = {}
  // 遍历当前节点chilren
  for (let i = 0, l = children.length; i < l; i++) {
    const child = children[i]
    // 获取data
    const data = child.data
    if (data && data.attrs && data.attrs.slot) {
      delete data.attrs.slot
    }
    if ((child.context === context || child.fnContext === context) &&
      data && data.slot != null
    ) {
      // 拿到插槽名称
      const name = data.slot
      const slot = (slots[name] || (slots[name] = []))
      if (child.tag === 'template') {
        slot.push.apply(slot, child.children || [])
      } else {
        slot.push(child)
      }
    } else {
      // 如果 data.slot 不存在,则是默认插槽的内容,则把对应的 child 添加到 slots.defaults 中
      (slots.default || (slots.default = [])).push(child)
    }
  }
  // 最后过滤一些空内容
  for (const name in slots) {
    if (slots[name].every(isWhitespace)) {
      delete slots[name]
    }
  }
  return slots
}

调用钩子函数beforeCreate

5.initProvide和initInjection

 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
function initProvide (vm) {
  var provide = vm.$options.provide;
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
    : provide;
  }
}
function initInjections (vm) {
  // 获取当前节点上的inject属性
  var result = resolveInject(vm.$options.inject, vm);
  if (result) {
    toggleObserving(false);
    // 遍历这些属性,defineReactive,将这些属性变成响应式的
    Object.keys(result).forEach(function (key) {
      /* istanbul ignore else */
      {
        defineReactive$$1(vm, key, result[key], function () {
          warn(
            "Avoid mutating an injected value directly since the changes will be " +
            "overwritten whenever the provided component re-renders. " +
            "injection being mutated: \"" + key + "\"",
            vm
          );
        });
      }
    });
    toggleObserving(true);
  }
}

6.initState

主要是初始化一些数据和属性,比如propsmethodsdatacomputedwatch,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.props) { initProps(vm, opts.props); }
  if (opts.methods) { initMethods(vm, opts.methods); }
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}

初始化props属性

 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
41
42
43
44
45
46
47
48
function initProps (vm, propsOptions) {
  // 获取当前节点的props属性值
  var propsData = vm.$options.propsData || {};
  var props = vm._props = {};
  var isRoot = !vm.$parent;
  // 用于保存当前组件的props里的key,以便之后在父组件更新props时可以直接使用数组迭代
  var keys = vm.$options._propKeys = [];
  var isRoot = !vm.$parent;
  if (!isRoot) {
    toggleObserving(false);
  }
  var loop = function ( key ) {
    keys.push(key);
    // validateProp验证当前prop[key]是否propsOptions定义的要求
    var value = validateProp(key, propsOptions, propsData, vm);
    {
      var hyphenatedKey = hyphenate(key);
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
          vm
        );
      }
      // 添加响应式属性,这样props里面的值改变之后,组件会自动更新视图
      defineReactive$$1(props, key, value, function () {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            "Avoid mutating a prop directly since the value will be " +
            "overwritten whenever the parent component re-renders. " +
            "Instead, use a data or computed property based on the prop's " +
            "value. Prop being mutated: \"" + key + "\"",
            vm
          );
        }
      });
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      proxy(vm, "_props", key);
    }
  };
	// 循环遍历key
  for (var key in propsOptions) loop( key );
  toggleObserving(true);
}

初始化methods属性

 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
function initMethods (vm, methods) {
  // props属性用于判断methods中的方法名是否和props的属性重名
  var props = vm.$options.props;
  for (var key in methods) {
    {
      if (typeof methods[key] !== 'function') {
        warn(
          "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
          "Did you reference the function correctly?",
          vm
        );
      }
      // 如果props中有同名属性,则报错
      if (props && hasOwn(props, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a prop."),
          vm
        );
      }
      // 如果key是以$或_开头则,也报错
      if ((key in vm) && isReserved(key)) {
        warn(
          "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
          "Avoid defining component methods that start with _ or $."
        );
      }
    }
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
  }
}

初始化data属性

 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
41
42
43
44
45
46
function initData (vm) {
  // 获取data属性
  var data = vm.$options.data;
  // 如果data是一个function,则调用getData返回里面的值,否则直接将data属性赋给_data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
  : data || {};
  // 如果data属性不是一个对象,设置data为空对象,并打印一个warn
  if (!isPlainObject(data)) {
    data = {};
    warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    );
  }
  // 获取data keys,props属性,methods属性
  var keys = Object.keys(data);
  var props = vm.$options.props;
  var methods = vm.$options.methods;
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    {
      // 当前data的key如果和methods中的方法重名,抛出警告
      if (methods && hasOwn(methods, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a data property."),
          vm
        );
      }
    }
    // 当前data的key如果和props中的属性重名,抛出警告
    if (props && hasOwn(props, key)) {
      warn(
        "The data property \"" + key + "\" is already declared as a prop. " +
        "Use prop default value instead.",
        vm
      );
    } else if (!isReserved(key)) {
      proxy(vm, "_data", key);
    }
  }
  // 双向数据绑定data
  observe(data, true /* asRootData */);
}

初始化computed属性

 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
41
42
43
function initComputed (vm, computed) {
  // $flow-disable-line
  var watchers = vm._computedWatchers = Object.create(null);
  // computed properties are just getters during SSR
  var isSSR = isServerRendering();

  for (var key in computed) {
    // 每个computed key所定义的方法
    var userDef = computed[key];
    // 将该方法赋值给getter变量
    var 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
      );
    }

    // 如果当前节点computed上未有key这个方法
    if (!(key in vm)) {
      // defineComputed方法主要是为当前节点的computed属性添加响应式更新方法
      // 可以在defineComputed定义最后看到 Object.defineProperty(target, key, sharedPropertyDefinition) 
      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);
      }
    }
  }
}

初始化watch属性比较简单,在做了相应的判断后直接添加一个响应式更新属性,这里不多赘述,调用完initState之后,会调用created钩子函数,此时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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// _init
vm._uid = 0
vm._isVue = true
vm.$options = {
  components: {
    KeepAlive,
    Transition,
    TransitionGroup
  },
  directives: {
    model,
    show
  },
  methods: {},
  computed: {},
  watch: {},
  filters: {},
  _base: Vue,
  el: '#app',
  data: function mergedInstanceDataFn(){}
}
vm._renderProxy = vm
vm._self = vm

// initLifecycle
vm.$parent = parent
vm.$root = parent ? parent.$root : vm

vm.$children = []
vm.$refs = {}

vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false

// initEvents	
vm._events = Object.create(null)
vm._hasHookEvent = false

// initRender
vm.$vnode = null  
vm._vnode = null
vm._staticTrees = null
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject

vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// 在 initState 中添加的属性
vm._watchers = []
vm._data
vm.message

7.执行完上面步骤后,会调用vm.$mount

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 保存之前定义的$mount方法,然后重写Vue.prototype.$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // 查找当前节点
  el = el && query(el)

 	// 如果事body元素或者documentElement元素则抛错
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
	
  // 拿到options
  const options = this.$options
  // 如果节点上没有render函数
  if (!options.render) {
    // 获取template,template可以是#id、模板字符串、dom元素
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          // 获取templatge innerHTML
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        // 如果不是#id、模板字符串,直接获取其innerHTML
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // 如果没有template,则获取el以及其子内容作为模板
      template = getOuterHTML(el)
    }
    if (template) {
      ......
    }
  }
  // 有render函数,直接执行mount.call(this, el, hydrating)
  return mount.call(this, el, hydrating)
}

function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

上面最后调用了mount.call(this, el, hydrating),其方法对应于

 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
41
42
43
44
45
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
	......
  // 调用beforeMount钩子函数
  callHook(vm, 'beforeMount')

  let updateComponent
	
  ......
  
  // 更新当前节点的方法
  updateComponent = () => {
    // vm._render会返回一个render字符串,_update其内部会调用patch方法来进行节点的增删改
    vm._update(vm._render(), hydrating)
  }

	// 创建一个watch对象,在调用updateComponent之前会先调用before方法
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        // 如果是更新节点,调用beforeUpdate钩子函数
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

   // 当页面渲染完成后,调用钩子函数mounted
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}