初学vue,你得晓得咱们是从new Vue开始的:

new Vue({  el: '#app',  data: obj,  ...})

那你感觉是不是很有意思,咱们new Vue之后,就能够应用他那么多的性能,可见Vue是暴进去的一个一个性能类函数,咱们进入源码一探到底:

import Vue from './instance/index'import { initGlobalAPI } from './global-api/index'//判断是不是服务端渲染import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'/** * 增加全局的API */initGlobalAPI(Vue)/** * 服务端渲染须要 */Object.defineProperty(Vue.prototype, '$isServer', {  get: isServerRendering})/** * 服务端渲染须要 */Object.defineProperty(Vue.prototype, '$ssrContext', {  get () {    /* istanbul ignore next */    return this.$vnode && this.$vnode.ssrContext  }})/** * 服务端渲染须要 */Object.defineProperty(Vue, 'FunctionalRenderContext', {  value: FunctionalRenderContext})/** * vue版本号 这里的'__VERSION__'为占位符,公布版本时将被主动替换 */Vue.version = '__VERSION__'export default Vue

那么咱们看到咱们的外围Vue来自'./instance/index'那咱们再去一探到底,可想而知外面必然有一个Vue函数类

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'// Vue构造函数必须应用new关键字实例化, 否则会抛出一个正告, 实例化Vue的时候会调用_init办法初始化// 这里options也是.vue文件中暴露出的对象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

能够看到外面有一个function Vue性能类,而且外面加载了initMixin,stateMixin等,这几个办法别离传入了Vue来初始化一些性能。
另外咱们能够在入口文件出看到initGlobalAPI这个办法,那么咱们关上initGlobalAPI所在的地位:./global-api/index

......export function initGlobalAPI (Vue: GlobalAPI) {  // config  // 提供获取配置项的办法, 不容许在这里设置配置项  const configDef = {}  configDef.get = () => config  if (process.env.NODE_ENV !== 'production') {    configDef.set = () => {      warn(        'Do not replace the Vue.config object, set individual fields instead.'      )    }  }  Object.defineProperty(Vue, 'config', configDef)  // exposed util methods.  // NOTE: these are not considered part of the public API - avoid relying on  // them unless you are aware of the risk.  // 注册全局工具API, 只对Vue失效。  Vue.util = {    warn,    extend,    mergeOptions,    defineReactive  }  //定义全局属性  Vue.set = set  Vue.delete = del  Vue.nextTick = nextTick  // 初始化options  Vue.options = Object.create(null)  ASSET_TYPES.forEach(type => {    Vue.options[type + 's'] = Object.create(null)  })  // this is used to identify the "base" constructor to extend all plain-object  // components with in Weex's multi-instance scenarios.  // 用_base属性来挂载Vue结构器  Vue.options._base = Vue  extend(Vue.options.components, builtInComponents)  //定义全局办法  initUse(Vue) // Vue.use  initMixin(Vue) // Vue.mixin  initExtend(Vue) // Vue.extend  initAssetRegisters(Vue)}

可见暴露出多个办法给全局,而Vue.util是一些工具办法:

import config from '../config'import { initUse } from './use'import { initMixin } from './mixin'import { initExtend } from './extend'import { initAssetRegisters } from './assets'import { set, del } from '../observer/index'import { ASSET_TYPES } from 'shared/constants'import builtInComponents from '../components/index'import {  warn,  extend,  nextTick,  mergeOptions,  defineReactive} from '../util/index'

那么咱们回到Vue性能函数类上:

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'// Vue构造函数必须应用new关键字实例化, 否则会抛出一个正告, 实例化Vue的时候会调用_init办法初始化// 这里options也是.vue文件中暴露出的对象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

咱们能够看到initMixin(Vue)执行了,那么咱们去读一下init的源码:

参考Vue3源码视频解说:进入学习

import config from '../config'import { initProxy } from './proxy'import { initState } from './state'import { initRender } from './render'import { initEvents } from './events'import { mark, measure } from '../util/perf'import { initLifecycle, callHook } from './lifecycle'import { initProvide, initInjections } from './inject'import { extend, mergeOptions, formatComponentName } from '../util/index'let uid = 0// 向Vue.prototype增加_init办法, 用于new Vue时 初始化一些配置项// 次要的性能是合并配置项, 初始化生命周期, 事件, 组件, 指令等等export function initMixin (Vue: Class<Component>) {  Vue.prototype._init = function (options?: Object) {    const vm: Component = this    // a uid    // 应用uid来辨别不同的vue实例, 以便前面的缓存性能应用    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)    }    // 如果是vue实例,则不须要被observe    vm._isVue = true    // 1、解决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      )    }    //2、renderProxy    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    //初始化vm状态 prop/data/computed/watch实现初始化    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)    }    // 配置项里有el属性, 则会挂载到实在DOM上, 实现视图的渲染    if (vm.$options.el) {      vm.$mount(vm.$options.el)    }  }}

浏览init.js源码之后,咱们能够看到其实就是这样一个程序:

  • option参数
  • renderProxy代理
  • vm的生命周期相干变量初始化
  • vm的事件监听
  • 初始化vm的状态
  • render & $mount

在Vue的原型上挂了一个_init办法,也就是说,咱们执行new Vue(options)的时候就会执行这个办法,并且咱们传过来的options能够通过vm.$options拜访到,那么mergeOptions()外部原理又是啥呢,而且咱们看到他接管到两个局部resolveConstructorOptions(),options并且交融在一起,那么咱们先钻研resolveConstructorOptions()

export function resolveConstructorOptions (Ctor: Class<Component>) {  let options = Ctor.options  if (Ctor.super) {    const superOptions = resolveConstructorOptions(Ctor.super)    const cachedSuperOptions = Ctor.superOptions    if (superOptions !== cachedSuperOptions) {      // super option changed,      // need to resolve new options.      Ctor.superOptions = superOptions      // check if there are any late-modified/attached options (#4976)      const modifiedOptions = resolveModifiedOptions(Ctor)      // update base extend options      if (modifiedOptions) {        extend(Ctor.extendOptions, modifiedOptions)      }      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)      if (options.name) {        options.components[options.name] = Ctor      }    }  }  return options}

那么问题又来了,let options = Ctor.options中的Ctor又是什么货色呢?咱们能够看到上面extend(Ctor.extendOptions, modifiedOptions)那咱们去找extend:

import { ASSET_TYPES } from 'shared/constants'import { defineComputed, proxy } from '../instance/state'import { extend, mergeOptions, validateComponentName } from '../util/index'export function initExtend (Vue: GlobalAPI) {  /**   * Each instance constructor, including Vue, has a unique   * cid. This enables us to create wrapped "child   * constructors" for prototypal inheritance and cache them.   */  // 为每个构造函数调配一个惟一的cid, 以便用于缓存  Vue.cid = 0  let cid = 1  /**   * Class inheritance   */  // 生成Vue结构器的子类  Vue.extend = function (extendOptions: Object): Function {    extendOptions = extendOptions || {}    // 如果cid已存在, 则利用缓存, 间接返回缓存的结构器    const Super = this    const SuperId = Super.cid    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})    if (cachedCtors[SuperId]) {      return cachedCtors[SuperId]    }    const name = extendOptions.name || Super.options.name    if (process.env.NODE_ENV !== 'production' && name) {      validateComponentName(name)    }    // 创立一个蕴含_init办法的子类, 并赋予惟一的cid    const Sub = function VueComponent (options) {      this._init(options)    }    Sub.prototype = Object.create(Super.prototype)    Sub.prototype.constructor = Sub    Sub.cid = cid++    // 合并配置项    Sub.options = mergeOptions(      Super.options,      extendOptions    )    Sub['super'] = Super    // For props and computed properties, we define the proxy getters on    // the Vue instances at extension time, on the extended prototype. This    // avoids Object.defineProperty calls for each instance created.    // 将props和computed都代理到vm实例上    if (Sub.options.props) {      initProps(Sub)    }    if (Sub.options.computed) {      initComputed(Sub)    }    // allow further extension/mixin/plugin usage    // 增加extend,mixin,use这些API    Sub.extend = Super.extend    Sub.mixin = Super.mixin    Sub.use = Super.use    // create asset registers, so extended classes    // can have their private assets too.    ASSET_TYPES.forEach(function (type) {      Sub[type] = Super[type]    })    // 将组件实例本身也挂载为一个属性, 用于递归组件    // enable recursive self-lookup    if (name) {      Sub.options.components[name] = Sub    }    // keep a reference to the super options at extension time.    // later at instantiation we can check if Super's options have    // been updated.    Sub.superOptions = Super.options    Sub.extendOptions = extendOptions    Sub.sealedOptions = extend({}, Sub.options)    // cache constructor    cachedCtors[SuperId] = Sub    return Sub  }}

那我么找到了Vue.extend,能够看进去这不就是实现了一个继承嘛。Sub继承自super,而后return进来。
所以resolveConstructorOptions就做两件事

  • Ctor.super 来判断该类是否是Vue的子类
  • if (superOptions !== cachedSuperOptions) 来判断父类中的options 有没有发生变化
    那么咱们晓得了resolveConstructorOptionsnew Vue(options),咱们的mergeOptions函数就是把他们合并的:
// 合并组件的配置项export function mergeOptions (  parent: Object,  child: Object,  vm?: Component): Object {  if (process.env.NODE_ENV !== 'production') {    checkComponents(child)  }  if (typeof child === 'function') {    child = child.options  }  // 序列化props, inject, directive  normalizeProps(child, vm)  normalizeInject(child, vm)  normalizeDirectives(child)  const extendsFrom = child.extends  if (extendsFrom) {    parent = mergeOptions(parent, extendsFrom, vm)  }  // child的mixins退出parent中  if (child.mixins) {    for (let i = 0, l = child.mixins.length; i < l; i++) {      parent = mergeOptions(parent, child.mixins[i], vm)    }  }  // 应用strat中的合并办法去顺次合并配置对象  const options = {}  let key  for (key in parent) {    mergeField(key)  }  for (key in child) {    if (!hasOwn(parent, key)) {      mergeField(key)    }  }  function mergeField (key) {    const strat = strats[key] || defaultStrat    options[key] = strat(parent[key], child[key], vm, key)  }  return options}

那么咱们写的一些组件和个性全副放在vm.$options外面了,那么下一步也就是renderProxy咱们有趣味的童鞋,能够根据援用门路去看看实现原理。

那么咱们initMixin函数执行完前两步之后须要执行的几个函数别离为

    // 顺次初始化配置项, 并调用生命周期钩子    vm._self = vm    initLifecycle(vm)    // 事件监听初始化    initEvents(vm)    initRender(vm)    callHook(vm, 'beforeCreate')    initInjections(vm) // resolve injections before data/props    //初始化vm状态 prop/data/computed/watch实现初始化    initState(vm)    initProvide(vm) // resolve provide after data/props    callHook(vm, 'created')

那咱们先来理解一下这几个函数别离干了啥:

1、initLifecycle

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}

2、initEvents

export function initEvents (vm: Component) {  vm._events = Object.create(null)  vm._hasHookEvent = false  // init parent attached events  const listeners = vm.$options._parentListeners  if (listeners) {    updateComponentListeners(vm, listeners)  }}

3、initRender

export function initRender (vm: Component) {  vm._vnode = null // the root of the child tree  vm._staticTrees = null // v-once cached trees  const options = vm.$options  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree  const renderContext = parentVnode && parentVnode.context  vm.$slots = resolveSlots(options._renderChildren, renderContext)  vm.$scopedSlots = emptyObject  // bind the createElement fn to this instance  // so that we get proper render context inside it.  // args order: tag, data, children, normalizationType, alwaysNormalize  // internal version is used by render functions compiled from templates  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)  // normalization is always applied for the public version, used in  // user-written render functions.  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)  // $attrs & $listeners are exposed for easier HOC creation.  // they need to be reactive so that HOCs using them are always updated  const parentData = parentVnode && parentVnode.data  /* istanbul ignore else */  if (process.env.NODE_ENV !== 'production') {    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)    }, true)    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)    }, true)  } else {    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)  }}

4、callback

export function callHook (vm: Component, hook: string) {  const handlers = vm.$options[hook]  if (handlers) {    for (let i = 0, j = handlers.length; i < j; i++) {      try {        handlers[i].call(vm)      } catch (e) {        handleError(e, vm, `${hook} hook`)      }    }  }  if (vm._hasHookEvent) {    vm.$emit('hook:' + hook)  }}

那咱们在initRender中能够发现初始化了一些性能,例如$lintener,$createElement,而在callback外面却间接调用了beforeCreate钩子函数。
前面还有

5、initInjections,resolveInject,initProvide

export function initInjections (vm: Component) {  const result = resolveInject(vm.$options.inject, vm)  if (result) {    toggleObserving(false)    Object.keys(result).forEach(key => {      /* istanbul ignore else */      if (process.env.NODE_ENV !== 'production') {        defineReactive(vm, key, result[key], () => {          warn(            `Avoid mutating an injected value directly since the changes will be ` +            `overwritten whenever the provided component re-renders. ` +            `injection being mutated: "${key}"`,            vm          )        })      } else {        defineReactive(vm, key, result[key])      }    })    toggleObserving(true)  }}
export function resolveInject (inject: any, vm: Component): ?Object {  if (inject) {    // inject is :any because flow is not smart enough to figure out cached    const result = Object.create(null)    const keys = hasSymbol      ? Reflect.ownKeys(inject).filter(key => {        /* istanbul ignore next */        return Object.getOwnPropertyDescriptor(inject, key).enumerable      })      : Object.keys(inject)    for (let i = 0; i < keys.length; i++) {      const key = keys[i]      const provideKey = inject[key].from      let source = vm      while (source) {        if (source._provided && hasOwn(source._provided, provideKey)) {          result[key] = source._provided[provideKey]          break        }        source = source.$parent      }      if (!source) {        if ('default' in inject[key]) {          const provideDefault = inject[key].default          result[key] = typeof provideDefault === 'function'            ? provideDefault.call(vm)            : provideDefault        } else if (process.env.NODE_ENV !== 'production') {          warn(`Injection "${key}" not found`, vm)        }      }    }    return result  }}
export function initProvide (vm: Component) {  const provide = vm.$options.provide  if (provide) {    vm._provided = typeof provide === 'function'      ? provide.call(vm)      : provide  }}

那么对于这三个函数是干嘛的?咱们先来理解一下:

provide/inject:

这对选项须要一起应用,以容许一个先人组件向其所有子孙后代注入一个依赖,不管组件档次有多深,并在起上下游关系成立的工夫里始终失效。如果你相熟 React,这与 React 的上下文个性很类似。

那就是相当于初始化依赖的关系,而initProvide根本没有什么内容,就是将$options里的provide赋值到以后实例上
写到这里那么激动人心的时刻到了--initState

export function initState (vm: Component) {  vm._watchers = []  const opts = vm.$options  if (opts.props) initProps(vm, opts.props)  if (opts.methods) initMethods(vm, opts.methods)  // 如果选项中不存在data,调用observe来观测一个空对象  if (opts.data) {    initData(vm)  } else {    observe(vm._data = {}, true /* asRootData */)  }  if (opts.computed) initComputed(vm, opts.computed)  // 这里须要额定判断一下opts.watch !== nativeWatch  // 是因为在Firefox下, Object实例上会有一个自带的watch属性, 须要判断这个watch必须是vue提供的watch  if (opts.watch && opts.watch !== nativeWatch) {    initWatch(vm, opts.watch)  }}

能够看出initState外面初始化了propsdatacomputedwatchmethods 那么咱们次要看一下initData,initProps的代码:

/** * data初始化, 和props基本一致 */function initData (vm: Component) {  let data = vm.$options.data  /**   * data字段有两种可选类型   * 当data是function时, 间接执行这个function 并将返回值作为data   * data也能够间接就是一个object, 但仅应该在root组件这样做, 因为间接应用data对象会导致多个雷同的组件持有同一个data对象的援用   * 而应用一个返回新对象的function就能够防止这个问题   * 详见 https://cn.vuejs.org/v2/style-guide/#%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE-%E5%BF%85%E8%A6%81   * 其实在initData之前的mergeOptions操作中曾经将data格式化为一个function   * 然而在initData和mergeOptions两头还有一个生命周期钩子beforeCreate被调用   * 这里应用typeof再次判断data的类型是为了避免在beforeCreate中扭转了vm.$options.data   */  data = vm._data = typeof data === 'function'    ? getData(data, vm)    : data || {}  // 避免开发者在写data选项时不按规矩返回一个对象  if (!isPlainObject(data)) {    data = {}    process.env.NODE_ENV !== 'production' && warn(      'data functions should return an object:\n' +      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',      vm    )  }  // proxy data on instance  // 将data的每个字段都代理到vm实例上, 并判断是否与methods, props抵触, 反复key会报错  const keys = Object.keys(data)  const props = vm.$options.props  const methods = vm.$options.methods  let i = keys.length  // 查看是否有反复的key, 这里能够看出props优先级最高, 其次是data, 最初是methods  // 真的呈现重名时,会依照优先级笼罩  while (i--) {    const key = keys[i]    if (process.env.NODE_ENV !== 'production') {      if (methods && hasOwn(methods, key)) {        warn(          `Method "${key}" has already been defined as a data property.`,          vm        )      }    }    if (props && hasOwn(props, key)) {      process.env.NODE_ENV !== 'production' && warn(        `The data property "${key}" is already declared as a prop. ` +        `Use prop default value instead.`,        vm      )    // isReserved用于判断一个key是否以$或者_结尾,vue不会在vm下面代理这些key    } else if (!isReserved(key)) {      proxy(vm, `_data`, key)    }  }  // observe data  // 使data变为响应式  observe(data, true /* asRootData */)}function initProps (vm: Component, propsOptions: Object) {  const propsData = vm.$options.propsData || {}  const props = vm._props = {}  // cache prop keys so that future props updates can iterate using Array  // instead of dynamic object key enumeration.  const keys = vm.$options._propKeys = []  const isRoot = !vm.$parent  // root instance props should be converted  // 应用toggleObserving能够设置shouldObserve为参数的值, 这里设为false是阻止观察者响应更新, 直到props初始化完结  if (!isRoot) {    toggleObserving(false)  }  // 遍历所有props选项, 并校验props  for (const key in propsOptions) {    keys.push(key)    const value = validateProp(key, propsOptions, propsData, vm)    /* istanbul ignore else */    if (process.env.NODE_ENV !== 'production') {      const hyphenatedKey = hyphenate(key)      // 如果应用了保留属性, 这里会报错, 保留属性包含key,ref,slot,slot-scope,is      if (isReservedAttribute(hyphenatedKey) ||          config.isReservedAttr(hyphenatedKey)) {        warn(          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,          vm        )      }      // isUpdatingChildComponent会在组件开始更新时置为true, 实现更新时置为false      // 如果子组件中更改一个props时会导致父组件也从新渲染, 这里就会抛出正告      defineReactive(props, key, value, () => {        if (vm.$parent && !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          )        }      })    } else {      // 使props变为响应式属性      defineReactive(props, key, value)    }    // static props are already proxied on the component's prototype    // during Vue.extend(). We only need to proxy props defined at    // instantiation here.    // 将props都代理到vm实例上, 以便代码中能够通过this.propName去拜访到对应的props    if (!(key in vm)) {      proxy(vm, `_props`, key)    }  }  // 初始化结束, 开启响应式更新  toggleObserving(true)}

上述代码实现了把propsdata变为响应式,前面的computedwatch临时不讲,后续专门有一篇文章来写他们。

那么initState之后咱们还会执行一个callback函数,传入的是created参数,调用钩子函数created这个时候也就是页面曾经创立了,并且下一个生命周期为beforMount,在讲挂载之前,肯定要讲响应式零碎原理