源码分析:new Vue() 数据如何渲染到页面,以超简单代码为例(2)

691 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

接上一节,继续分析new Vue()过程:

<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

$mount过程

上一节我们分析了data的init过程,在init过程结束后,执行如下代码:

if (vm.$options.el) {
  // initData之后 该挂载了
  vm.$mount(vm.$options.el);
}

这个$mount在哪定义的呢,在platforms/web/runtime/index.js中有定义

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // el转换成DOM 这里el是 <div id="app">{{ message }}</div>
  el = el && inBrowser ? query(el) : undefined
  // 执行mountComponent函数
  return mountComponent(this, el, hydrating)
}

如果是compiler版本的vue,可以自定义template(我们以这个版本来进行分析),则有如下代码:

// 缓存上面代码的$mount函数
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // 查找元素 这里返回<div id="app">{{ message }}</div>
  el = el && query(el)

  /* istanbul ignore if */
  // 元素不能是body或html
  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
  // render函数 手写render函数则直接跳过这个逻辑, 如果没有写render函数,则分析template,进行编译生成render函数
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          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) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    // 存在template
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      // 进行编译 生成render函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      // options中生成了属性render函数
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // 执行上面的mount
  return mount.call(this, el, hydrating)
}
/**
 * Query an element selector if it's not an element already.
 */
function query (el) {
  if (typeof el === 'string') {
    // 如果el不是元素,是字符串,则在页面中查找这个元素并返回否则报错
    var selected = document.querySelector(el);
    if (!selected) {
      "development" !== 'production' && warn(
        'Cannot find element: ' + el
      );
      return document.createElement('div')
    }
    return selected
  } else {
    return el
  }
}

这里的操作主要就是将template转换成render函数进行后续操作,其中的编译过程较为麻烦,笔者在后面会出文章进行分析。 接下来执行上面的代码后,最后执行了mountComponent函数,定义在src/core/instance/lifecycle.js中,源码如下:

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // 这里的el是 <div id="app">{{ message }}</div>
  vm.$el = el
  if (!vm.$options.render) {
    // 如果还没有render,则报错了,必须要有render函数
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        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
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  // 调用生命周期函数beforeMount,挂载前,马上开始挂载了,大家注意!
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  // 性能埋点相关 跳过 执行else逻辑
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    // 定义了updateComponent函数,这是大佬
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  // 实例化watcher,这里叫渲染watcher(RenderWatcher),为渲染而生的watcher
  // 注意传入的参数,updateComponent是第二个参数,noop是个空函数
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  // 这里是根实例没有 $vnode,所以在实例化Watcher之后,执行mounted生命周期函数
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

这里面主要是定义了一个updateComponent方法,然后实例化了一个渲染watcher,实例化的过程中干了什么呢,现在来看看这个Watcher的真面目,源码在src/core/observer/watcher.js中:

/* @flow */
let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  computed: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  dep: Dep;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component, // Vue
    expOrFn: string | Function, // 这里是updateComponent
    cb: Function, // 空函数noop
    options?: ?Object, // before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } }
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      // 添加了_watcher属性指向自身,renderWatcher专有属性
      vm._watcher = this
    }
    // 前面定义过的_watchers
    vm._watchers.push(this)
    // options // before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') 
    // 在这次分析中不用看
    if (options) { 
      this.deep = !!options.deep
      this.user = !!options.user
      this.computed = !!options.computed
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.computed = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.computed // for computed watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    // 上面我没注释的都不用关注, 这里的expOrFn是updateComponent,赋值给this.getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      // 执行这个get,代码在下面
      this.value = this.get()
    }
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    // pushTarget这个是收集依赖的,这次分析我们不用关注
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 看这儿,我们执行了this.getter,也就是执行了 updateComponent函数
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    
    return value
  }
}

那updateComponent函数到底发生了什么呢?点击此处前往下一节