小白看源码之Vue篇-1:new Vue()干了什么?

前言

距离本小白接触前端开发也已经快将近一年了,从开始的连闭包的原理都须要了解一个晚上(虽然目前深刻其原理我也说不明白)到如今可以本身独立完成一个简单的单页webapp,回顾近一年的时间,也是按照前人留下的足迹,一步一个脚印的走,从html到css再到js再到jQuery库,再到webpack,直到Vue。一路走来较为顺利,单也有磕绊,也有疑惑。开始接触Vue的时候,甚为惊艳!居然有如此之写法,简洁,方便。可是使用过程固然难免充满了疑惑,为何是这样的?为何能这样写。在通过近一年的打磨以后,本小白也决定开始研究、研读Vue源码,但愿在Vue3.0来临之际,将2.6.X源码研究一遍,为日后3.0打基础,而且将这一年来心中的很多疑惑悉数解开!也但愿本文对一样在Vue道路上的朋友有所帮助~javascript

1、准备工做

磨刀不误砍柴工,再开始研读以前,咱们须要了解一下Vue的文件的架构以及Vue源码编写使用到的静态类型检查器FLOW。css

文件架构

咱们从github上面下载最新的vue的源码后,能够看到vue的源码项目文件仍是很是庞大的,和通常的webpack相似,咱们Vue源码也存在src文件当中。 html

compiler文件夹存放的代码是与模板解析相关的后续的文章也会专门讲到。

core文件就是咱们这一篇要讲得的核心也是Vue实例初始化的核心所在。前端

platform文件夹存放的是咱们的vue如何进入到运行时的,区分是经过cli打包的文件仍是直接在html中引用的状况等。vue

server文件中存放生成render模板编译的相关代码,例如咱们的template是如何编译成render函数,逻辑在这个文件夹中。java

FLOW

FLOW是一个静态类型检查器,因为Vue中存在大量的类,引入flow静态检查器来确保类属性不出错,用法相似于TypeScript,这里就很少展开介绍了(实际上是不会)。node

2、new Vue()

一、Class Vue

正文开始了!首先,咱们找到Vue类定义的位置src/core/instance/index.jswebpack

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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)
}

initMixin(Vue) 
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
复制代码

在这里看到Vue类的定义很是"简单"= =;作了一个判断防止调用Vue函数,而后就是一个初始化this._init(options)。而_init 就是在initMixin(Vue)是混入到类中的。options就是咱们常常main.js写的那个传入的对象:git

new Vue({
  render: h => h(App),
}).$mount('#app')
复制代码

那么,接下来 咱们就要看一下initMixin到底对Vue作了什么,给这个类添加了什么功能,以及_init()函数到底作了哪些事情。github

二、initMixin()

从上面代码能够看到initMixin在同级目录下init.js文件中,咱们来看一下initMixin到底干了什么。

export function initMixin (Vue: Class<Component>) {
    Vue.prototype._init = function (options?: Object) { 
    //some codes
    }
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  //some codes
}

export function resolveConstructorOptions (Ctor: Class<Component>) {
  //some codes
}

function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  //some codes
}

复制代码

好吧整个文件去掉逻辑部分仍是很清晰的,暴露四个方法,能够看到initMixin就是只为Vue添加了一个_init方法,那么接下来咱们就重点来看一下_init方法,在Vue实例化的过程当中,作了什么吧~

_init()

首先,先把源码贴上~

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) //实例的首次挂载
    }
  }
复制代码

一、mergeOptions

咱们一点点来说,首先判断咱们传入的options,根据是否存在options._isComponent来判断如何处理options。Vue的初始化逻辑固然是走下面的,经过mergeOptions()合并了父类(这里是指Vue)options和当前options。那么好奇的童鞋确定要问了,Vue哪来的options??!!这里卖个关子,后面会讲到的~仍是比较重要的一块,因此留个悬念,也算是加深印象~

二、一系列的initXXX()

能够看到接下来就是一系列的init初始化,咱们本章大体介绍一下每一个init都是干啥的,具体展开讨论会放到后面专门的文中介绍~首先是initLifecycle:它主要是针对生命周期作一些初始化;initEvents:对vue的监听事件的初始化;initRender:初始化Vue的render方法,本文会花必定篇幅展开;callhook:beforeCreate:调起预先设定的beforeCreate钩子函数(没错生命周期钩子函数就在这里慢慢开始了);initInjections(vm):后代组件injection相关初始化;initState(vm):初始化data、props、methods等属性,数据双向绑定的核心所在,以后文章会详细展开。initProvide(vm):后代组件initProvide相关初始化;callHook(vm, 'created'):调起预先设定的created钩子函数。

三、vm.$mount(vm.$options.el)

最后一步就是调用vm.$mount()方法进行实例的挂载。咱们能够看到,若是咱们传入的属性若是有el属性,才会调用,否则是不会调用的。因此咱们在new Vue是会有两种状况:

new Vue({
  render: h => h(App),
}).$mount('#app')//不走_init()的mount本身调用

new Vue({
  el:'#app'//_init直接调用mount
  render: h => h(App),
})
复制代码

到这里,_init(options)的流程也就结束了,虽而后面的文章咱们还会无数次提到他。。。接下来咱们就来一块儿看一下vm.$mount()到底干了些什么,以及具体他是如何挂载咱们的Vue实例的。

三、vm.$mount(el)

vm.$mount(el)定义在src/platforms/web/index.js咱们一块儿来看一下代码。

Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
复制代码

看来只是一个封装,咱们继续去找mountComponent(this, el, hydrating),代码在src/core/instance/lifecycle.js里面。咱们看一下代码:

export function mountComponent ( //vue实例挂载的方法 vm: Component, el: ?Element, hydrating?: boolean ): Component {
  vm.$el = el
  if (!vm.$options.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
        )
      }
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  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 = () => {
      vm._update(vm._render(), hydrating) //挂载实例,调用
    }
  }

  new Watcher(vm, updateComponent, noop, { 
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        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
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}
复制代码

咱们一步步看他的逻辑来看他的逻辑。一、!vm.$options.render判断若是实例不存在render函数,则建立一个空的vnode。二、callHook(vm, 'beforeMount')原来这个钩子函数在这儿~三、调用了_update()里面两个参数,vm._render()建立了一个node节点,接下来咱们会重点展开。

let updateComponent () => {
      vm._update(vm._render(), hydrating) 
    }
复制代码

四、new Watcher()添加了一个观察者,这是一个render Watcher监听数据变化后展开节点渲染。(后面介绍watcher的时候会重点讲)目前咱们只要知道watcher在初始化会调用传入的updateComponent函数,从而触发渲染和实例挂载。五、callHook(vm, 'mounted'):mounted钩子函数都不陌生。到这里vm.$mount函数也就走完了!

3、总结

原本打算把vm._update(vm._render(), hydrating)也放在这里面讲的,可是一想,若是仓促展开,可能有些细节回顾及不到并且vm._update()vm._render()也是比较重要的两块一块涉及patch(确定就会涉及到diff算法)另外一块涉及到Vnode建立,如今本小白也处于比较没有思绪的状况,不知如何展开你们会比较容易接受,须要再整理整理大纲在接着往下来写~因此就先到这儿吧!在这里也贴一下目前本小白学习整理的大纲:Vue源码解读

最后的最后打个小小的广告~

有大佬们须要短信业务,或者号码认证的能力的话,能够看看这里!中国联通创新能力平台 运营商官方平台!没有中间商赚差价~

相关文章
相关标签/搜索