vue2.0源码-initMixin(一)

Vue initMixin

接下来我们看看new 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)

在我们对new Vue()时,会调用this._init()方法开始生成创建出一个vue实例,这个方法是initMixin中进行定义

在init.js我们可以找到这个方法,我们来对这个方法来进行拆分分析

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

performance 性能

下面这段代码是在window环境下,使用window.performance来进行前端性能监控

mark方法就是window.performance.mark

measure方法就是window.performance.measure

    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)
    }
    
    ...
    
     /* 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)
    }

我们可以这样理解上面的mark就是打点,一个开始节点,一个结束节点,然后通过measure获取两个节点之间的情况,并记录下来

vm._name 如果是根组件拿到的就是 <Root> 如果是子组件,如<demo></demo>这样的组件,我们拿到的就是<Demo> 因此这里的measure(`vue ${vm._name} init`, startTag, endTag)就等于measure(`vue <Root> init`, startTag, endTag)

接下来我们可以通过在控制台输入performance.getEntriesByName("vue <Root> init");来获取数据

如果不理解里面各参数代表的意义,我们可以在刚刚的代码上加入performance.now来查看

 let beginTime,endTime
 if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      beginTime = performance.now();
      console.log('开始时间',beginTime)  //打印开始时间
      mark(startTag)
    }
    
    
     if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      endTime = performance.now();
      console.log('结束时间',endTime); //打印结束时间
      console.log('相差时间',endTime - beginTime); //打印相差时间
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
      console.log(performance.getEntriesByName("vue <Root> init"))
    }

由此我们便可以知道startTime就是开始时间,duration就是开始时间和结束时间的时间差

beforeCreate 生命周期

接下来我们看在beforeCreate之前,都进行了哪些初始化的操作

resolveConstructorOptions(vm)

    // merge options
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }

options._isComponent是判断元素是否为组件,首先我们可以在用debugger的方式,看下这段代码做了些什么操作

首先看没有执行之前,vm是什么样的

接下来看执行之后

通过断点的方式,我们可以看的出来,这段代码主要是用于创建$options合并我们传入vue的options;

initProxy(vm)

通过刚刚的断点法,我们很快就可以知道initProxy方法,给我们vm的_renderProxy进行了赋值操作

initProxy = function initProxy(vm) {
    if (hasProxy) {
      // determine which proxy handler to use
      const options = vm.$options;
      const handlers =
        options.render && options.render._withStripped
          ? getHandler
          : hasHandler;
      vm._renderProxy = new Proxy(vm, handlers);
    } else {
      vm._renderProxy = vm;
    }
  };

我们可以先简单了解下,hasProxy是利用/native code/.test(Proxy)判断当前浏览器是否支持Proxy,不支持则将vm直接赋值给vm._renderProxy,如果在支持的情况下,先判断是否有render的写法,如果没有,则利用Proxy去改写vm的has方法,也就是let key in target这一类操作,如果存在,则改写get方法。具体可以去了解Proxy的用法。

initProxy方法使用Proxy主要是在操作vm属性的时候,判断该属性是否存在,给出一些警告提示之类的。

initLifecycle(vm)

这次我们直接看源码,可以从很清楚的看出,initLifecycle主要处理组件的父子关系,定义了parentparent,children,refsrefs,root等等属性

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
}

我们可以看下的具体处理方式

  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

由于是处理父子关系,应该我们可以再定义一个组件

然后进行打印和断点测试都可以,我们就继续选择断点

首先是第一次进入,很明显parent是空的,因为这次进入的是根组件,接下来就是demo组件了

这个时候,我们可以很清楚的看到组件demo的vm.$options.parent是存在值的,就是我们的根组件,而在获取到父组件以后,还会判断options.abstract,这个是用来判断,父组件是否为抽象组件,如<keep-alive><transition>这一类是不会成为父组件的,因此要对此进行判断。

还有一些属性,我们等到被赋值的时候,再来看具体效果。

initEvents(vm)

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

首先我们看第一句代码,这句代码创建一个以null为原型的空对象,并赋值给了_events

vm._events = Object.create(null)

第二行,主要用于判断是否存在hook类的事件,如果存在,在vm.$on的时候,会赋值为true

vm._hasHookEvent = false

下面这部分代码,我们主要看下具体作用。

 // init parent attached events
 const listeners = vm.$options._parentListeners
 if (listeners) {
 updateComponentListeners(vm, listeners)
 }

我们可以先在组件上定义两个事件

接着进行断点,可以看出根组件进入时为undefined

demo组件进入时,可以看出listeners就是我们通过v-on或者@绑定在demo子组件上的事件。

当子组件上存在绑定的事件时,则调用updateComponentListeners,使用vm.$on的方法,将事件添加到vm.__events上,并判断是否存在hook类的事件,如果存在则将vm._hasHookEvent赋值为true

initRender(vm)

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

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

从源码中可以在initRender定义了$createElement这个方法,并调用defineReactive使用Object.defineProperty定义了$attrs,$listeners两个属性。

以上就是beforeCreate之前vue进行的操作了。我们可以通过断点的方式看下在beforeCreate之前,我们可以拿到哪些值

根组件

demo组件

以上就是beforeCreate之前的一些简单理解了,在后面我们在对created之前的操作进行分析,created分析结束以后,我们再来理一理其中的一些方法具体的实现方式。