Vue源码系列 -- 数据驱动流程分析(2)

122 阅读2分钟

Vue实例挂载($mount)

承接上一节数据驱动流程分析1
在实例化Vue是执行了this._init()中最后执行的if (vm.$options.el) { vm.$mount(vm.$options.el) },也就是$mount方法。
vm.mount会最终调用src/platforms/web/entry-runtime-with-compiler.js中的mount

// src/platforms/web/entry-runtime-with-compiler.js
// 先将原型链上的$mount方法进行缓存,然后重新定义了$mount方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
	/*  query(el) 方法 将el转化成DOM对象
	if (typeof el === 'string') {
		const selected = document.querySelector(el)
  	if (!selected) {
    		process.env.NODE_ENV !== 'production' && warn('
				Cannot find element: ' + el
			)
    		return document.createElement('div')
  	}
  	return selected
	} else {
  	return el
	}
	*/
  /* istanbul ignore if */
  // 判断了el不能为 html元素 和 body元素
  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
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
		// 这里我们没有template参数
    if (template) {
      // ....
    } else if (el) {
      template = getOuterHTML(el)
		// getOuterHTML 将DOM结构转化成 String 具体可见 element.outerHTML api
    }
    if (template) {
      // ...
		// 拿到编译相关的配置,就是这里将template转化成了render函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
		// 将render函数加入到options中去
      options.render = render
		// ...
    }
  }
	// 最后执行一开始保存下来的mount方法
  return mount.call(this, el, hydrating)
}

这一块的代码主要做了两件事情: 1.拿到template 2.将template转化成render 然后执行了mount()方法

// src/platforms/web/runtime/index
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
	// el 已经是DOM结构了
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

最终调用了mountComponent()方法

// src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
	// 此处判断如果没有render函数的话,会抛出警告,也恰恰说明了Vue实际接受的是一个render函数
	// template在complier版本下会被转化成render函数
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    //...
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
	  //...
    }
  }
	// 出发了beforeMount钩子
  callHook(vm, 'beforeMount')

  let updateComponent
	// 性能埋点相关
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    // ...
  } else {
    // 最后执行了updateComponent方法
	  /* updateComponent 被赋值成了一个函数,vm._update(vm._render(), hydrating) */
    updateComponent = () => {
		// vm._update() 是将实例生成为Virtual Dom
		// vm._render() 将Virtual Dom 渲染为真是Dom
      vm._update(vm._render(), hydrating)
    }
  }

	// 将updateComponent作为new Watcher的参数传入
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true )
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

Watcher 是Vue中非常核心的概念,渲染Watcher , computed Wathcer 还有user Watch 都是通过 Watcher这个class实现(观察者模式)。

 class Watcher {
  vm: Component;
  lazy: boolean;
  getter: Function;
  value: any;
	// ...	

  constructor (
    vm: Component,
    expOrFn: string | Function,  // expOrFn = updateComponent()
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    // ...
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy // 为false
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    // ...
    // parse expression for getter
    if (typeof expOrFn === 'function') {
		// 赋值给this.getter 
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      // ...
    }
 	  // 执行 this.get()方法 并 赋值给value
    this.value = this.lazy ? undefined : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
		// 执行了this.getter 方法
		// value = vm._update(vm._render(), hydrating)
      value = this.getter.call(vm, vm)
    } 
	// ...
}

将template转化成render函数,render函数将DOM转化成了 虚拟dom 然后通过vm._update渲染为真实DOM