Vue2.0源码解读系列 - 打开神秘礼盒之合并选项(1)__Vue.js
发布于 3 年前 作者 banyungong 1254 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

文章篇幅较长, 建议花整块时间阅读分析。 另外由于篇幅过长, 本文分三篇文章产出, 便于大家理解与阅读。

上一篇文章分析中,我们分析了vue的整体的运行机制, 其中, 我们知道Vue在初始化的时候调用了init()函数: init

在instance.js文件中, 调用了initMixin()方法。然后我们找到init.js中, 对initMixin函数的定义, 中间有这么一段代码:

    // merge options
    if (options && options._isComponent) { // 如果当前Vue实例是组件,执行initInternalComponent
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options) // 该方法主要就是为vm.$options添加一些属性
    } else {
      // 否则就是实例化Vue对象,调用mergeOptions
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }

这段代码是合并选项, 那么到底是如何进行合并选项options呢?

首先我们来看直接实例化Vue, 也就是:

      // 否则就是实例化Vue对象,调用mergeOptions
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )

可以看到, 调用了mergeOptions(参数1, 参数2, 参数3)函数

  • 参数1: resolveConstructorOptions(vm.constructor);
  • 参数2:options || {};
  • 参数3: vm

参数2, 是我们创建Vue实例的时候传入的options 参数3, Vue自身的实例

所以, 主要看参数1: resolveConstructorOptions(vm.constructor)函数。

本篇幅就来介绍, vue是如何解析当前实例构造器上的options的 ##resolveConstructorOptions函数 解析当前实例构造函数上的options

/**
 * @param {*} Ctor: vm.constructor
 * 这个方法要分成两种情况来说明
 * 第一种是Ctor是基础Vue构造器的情况
 * 另一种是Ctor是通过Vue.extend方法扩展的情况。
 */
export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  // 有super属性,说明Ctor是Vue.extend构建的子类
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions // Vue构造函数上的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
      }
    }
  }
  return options
}

####我们先看Ctor是基础Vue构造器的情况, 即通过new Vue()构造子类

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  // 有super属性,说明Ctor是Vue.extend构建的子类
  ...
  return options
}

Ctor其实就是Vue构造器,Ctor.options就是Vue构造函数上的options, 这个时候直接返回了。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>new Vue</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    {{message}}
  </div>
  <script>
    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      },
      created: function (){
        console.log('this.constructor.options ', this.constructor.options)
      }
    })
  </script>
</body>
</html>

我们打印Vue构造器上的options对象: vm.constructor.options

可以看到基础构造器上有components, directives, filters, _base四个基础属性, 这些属性是从哪里定义的呢?

既然我们是初始化整个框架,那么一定时需要框架项目能够运行, 因此我们找到package.json文件, 找到运行命令scripts: "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",

然后定位到scripts/config.js, 找到target: :web-full-dev

// Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },

可以看到入口文件是:web/entry-runtime-with-compiler.js 在platforms/web/entry-runtime-with-compiler.js中, 我们看到这么一行代码: import Vue from ‘./runtime/index’

在该文件中:

import Vue from 'core/index'
...
import platformDirectives from './directives/index'
import platformComponents from './components/index'
...
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

关于Vue.options的设置就是这么几行代码, 然后我们分别代开platformDirectives、platformComponents文件 directives platformDirectives文件导出了Vue的全局指令v-model, v-show

components platformComponents文件导出了Vue的全局动画组件Transition, TransitionGroup组件。

结合上面我们打印出的vm.constructor.options的截图 vm.constructor.options

我们看到,除了上面两个文件定义的directives中的model, show以及components中的Transition, TransitionGroup以外, components中的KeepAlive组件还有filters, _base属性我们没有看到。

我们再回到core/index文件入口(第一篇文章中, 找到instance.js文件也是从这里进入的), 看到:

import Vue from './instance/index' // 第一篇文章的入口
import { initGlobalAPI } from './global-api/index'
...
initGlobalAPI(Vue)
...

进入到global-api/index.js, 搜索Vue.options:

  import { ASSET_TYPES } from 'shared/constants'
  import builtInComponents from '../components/index'
  ...

  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.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

// shared/constants
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

// ../components/index
import KeepAlive from './keep-alive'
export default {
  KeepAlive
}

因此, 我们就找到了filters,_basecomponents中的KeepAlive。Vue构造函数的options对象就很清晰了。

下面, 总结一下寻找基础Vue构造函数options选项的路径:

  • 1、new Vue({})实例化
  • 2、打印出vm.constructor.options, 发现有components, directives, filters, _base四个属性
  • 3、既然是项目初始化, 找到package,json文件, 找到项目启动命令: npm run dev
  • 4、根据dev命令行定位的scripts/config文件, 找到了入口文件 web/entry-runtime-with-compiler.js
  • 5、通过该入口文件中的import Vue from './runtime/index', 我们首先找到了components中的TransitionTransitionGroup组件;找到了directives属性中的modelshow指令
  • 6、再通过该文件import Vue from './runtime/index', 发现import Vue from 'core/index'
  • 7、回到入口文件core/index, 找到initGlobalApi, 进入global-api/index.js文件
  • 8、搜索Vue.options, 找到了剩下的components中的keepAlive组件以及filters_base属性 经过上面寻找路径, 我们最终找全了Vue构造器的最初的options选项:components, directives, filters, _base vm.constructor.options

通过基础构造器new Vue()创建实例

####再看Ctor.super, 即通过Vue.extend构造子类

// Vue.extend函数在实例上加了一个super属性
Vue.extend = function (extendOptions: Object): Function {
  ...
  Sub['super'] = Super
  ...
}

/**
 * @param {*} Ctor: Ctor.super
 * 这个方法要分成两种情况来说明
 * 第一种是Ctor是基础Vue构造器的情况
 * 另一种是Ctor是通过Vue.extend方法扩展的情况。
 */
export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options // 相当于vm.constructor.options
  // 有super属性,说明Ctor是Vue.extend构建的子类
  if (Ctor.super) { // 相当于vm.constructor.super
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions // Vue构造函数上的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
      }
    }
  }
  return options
}

我们看下通过Vue.extend()创建实例,举个例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>new Vue</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app2"></div>
  <script>
    var Profile = Vue.extend({
      template: '<p>123</p>',
    })
    console.log('new profile super ', new Profile().constructor.super)
    console.log('new profile superOptions', new Profile().constructor.superOptions)
    console.log('new profile constructor ', new Profile().constructor)
    console.log('new profile constructor options', new Profile().constructor.options)
    new Profile().$mount('#app2')
  </script>
</body>
</html>

我们找到Vue.extend函数的定义:

export function initExtend (Vue: GlobalAPI) {
 ...

  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    ...
    const Sub = function VueComponent (options) {
      this._init(options) // 这里执行了Vue构造器上的_init函数
    }
    Sub.prototype = Object.create(Super.prototype) // 继承Super, 也就是Vue
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super
    ...

    // 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(VueComponent)继承了Super(Vue), 然后在Sub构造器上添加了

  • options: 融合了Super.options和extendOptions(我们在Vue.extend()中传入的选项)
  • super属性: 指向Super(Vue)
  • superOptions属性: Super.options
  • extendOptions属性: 传入的参数
  • sealedOptions属性:Sub.options(见第一个)

这个时候, 我们打印一下super, superOptions, extendOptions, vm.constructor, vm.constructor.options 通过Vue.extend创建实例

从通过Vue.extend创建实例的图中, 可以看到

  • vm.constructor.super的值其实就是Vue构造器。而vm.constructor.superOptions其实是继承自基础Vue构造器上的选项。
  • extendOptions是我们传入的参数。
  • vm.constructor是子类的构造器VueComponent,
  • vm.constructor.options的值,除了继承的四个基础属性: components, directives,filters, _base以外, 还有我们自定义的template属性。
  • superOptions + extendOptions = vm.constructor.options(vm.$options)

所以:

if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions // Vue构造函数上的options
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options. 将自身的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
      }
    }
  }

这段代码的意思就是:将传入的选项以及父级Vue构造器上的选项进行合并返回options。

当然,中间还有一个检查选项是否发生改变的函数resolveModifiedOptions, 就不深入分析了。

所以,resolveConstructorOptions函数的作用是:解析当前实例构造函数上的options

在Vue.extend方法中涉及到ES5原型继承的知识, 如果不熟悉的话线下可以翻阅一下书籍。

到此我们就详细介绍了vue解析当前实例构造器上的options选项的原理, 下一篇我们继续介绍外层合并选项函数mergeOptions函数。

终于熬完了合并选项的分析(三篇文章), 每天抽出睡觉的时间来啃这块, 非常不容易,促进自己成长, 希望也能给小可爱们带去一些启发, 如果有地方觉得说的不清楚的欢迎在下方评论区提问,讨论~

看到这里的小可爱们, 可顺手点个赞鼓励我继续创作, 创作不易,一起学习, 早日实现财富自由~

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 爱钻研的果冻 原文链接:https://juejin.im/post/6932285432281432071

回到顶部