Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 源码学习(四)- initMixin #11

Open
zhuping opened this issue May 27, 2019 · 0 comments
Open

Vue 源码学习(四)- initMixin #11

zhuping opened this issue May 27, 2019 · 0 comments

Comments

@zhuping
Copy link
Owner

zhuping commented May 27, 2019

通过上篇文章 Vue 源码学习(二)- 构造函数,我们知道了 Vue 的构造被多个函数包装处理,所以我们今天来分析下第一个包装函数 initMixin,它到底做了哪些处理呢?

初始化

我们可以看到,在 initMixin 方法中,在 Vue 的原型链上增加了 _init 方法,这个方法是不是很眼熟,在分析构造函数的时候,它就调用了这个方法,原来是在这里定义的。

// src/core/instance/init.js

let uid = 0

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

首先声明了常量 vm,指向当前 Vue 实例,然后添加了一个唯一标示 _uid,其值为 uiduid 初始值为 0,所以每次实例化一个 Vue 实例之后,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)
}

我们这边不做详细介绍,主要还是来看下中间部分的内容:

// 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')

我们来逐行分析下,首先是在 Vue 实例上添加 _isVue 属性,并设置其值为 true。看注释知道是为了避免被响应系统观察,也就是说,如果一个对象拥有 _isVue 属性并且值为 true,那么就代表该对象是 Vue 实例,这样就可以避免被观察。

接下来是一个 options && options._isComponent 判断,它是用来初始化内部组件用到的,这块内容等讲到组件部分再来分析,所以先看 else 部分。

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)

Vue 实例上添加了 $options 属性,在官方文档中,我们也能看到它是用于当前 Vue 实例的初始化选项,它是通过 mergeOptions 函数创建而来。该函数有三个参数,第一个是通过函数 resolveConstructorOptions 返回得到,第二个是调用 Vue 构造函数时透传进来的对象,第三个参数是当前的 Vue 实例。

在讲 mergeOptions 前,我们先来看下 resolveConstructorOptions 函数,看名字应该是用来解析构造函数的 options,传的参数也是实例的构造函数。看下具体实现:

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
}

通过上面调用时传递的参数 vm.constructor 知道,这里的 Ctor 是当前实例的构造函数,也就是 Vue 本身。那么这里一定是 Vue 吗?通过下面 if 判断中的 super 知道,Ctor 也有可能是个子类,因为只有之类才有 super 属性。也就是说 Ctor 有可能是通过 Vue.extend 创造的一个子类。

有了上面的理解,下面的 if 判断也比较好理解了。如果当前是 Vue 的实例,那么直接返回 Vue.options 的值。根据前面文章构造函数篇章知道,Vue.options 的值是通过全局 global-api 来初始化的,内容如下:

Vue.options = {
  components: {
    KeepAlive,
    Transition,
    TransitionGroup
  },
  directives: {
    model,
    show
  },
  filters: Object.create(null),
  _base: Vue
}

那么如果当前是子类的实例呢?

const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions

通过递归调用 resolveConstructorOptions 来获取父类的 options

Ctor.superOptions 的值我们前面的文章 Vue.extend 中提到

Sub.superOptions = Super.options

也就是说它是通过 Vue.extend 继承而来的父类的静态属性 options 值。

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

superOptionscachedSuperOptions 不相等时,更新 Ctor.superOptions。这种情况比如如下例子:

var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
});
Vue.mixin({ data: function () {
return {
  firstName: 'Walter',
  lastName: 'White',
  alias: 'Heisenberg'
}
}});
new Profile().$mount('#mount-point');

因为在执行 Vue.mixin 时,修改了 Vue.options 的值:

Vue.mixin = function (mixin: Object) {
  this.options = mergeOptions(this.options, mixin)
}

如果没有这个 if 判断,页面将只显示 aka,因为 Vue.options 上的值还是旧的。

接下来通过 resolveModifiedOptions 方法来检测“自身”的 options 是否发生变化,如果有变化,返回修改的值。Ctor.extendOptions 作为传递给 Vue.extend 的参数,和 superOptions 合并并赋值给 options,最终返回 options

小结

上面说了这么多,似乎都是和 options 有关。而且后面的内容,都会使用到这个 options。既然 options 这么重要,我们下一篇来说说 options 的合并策略 mergeOptions 函数是怎么实现的。

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant