Vue 2.6.13 源码解析

Posted 白瑕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 2.6.13 源码解析相关的知识,希望对你有一定的参考价值。

文章目录


前言

Vue 2.6.13 源码分析 (一)

你需要有Vue 2.6.13的包.

https://github.com/vuejs/vue/releases/tag/v2.6.13

一、入口

1.index.js

src/core/instance/index.js

负责Vue配置对象的直接接收, 即

new Vue()

需要传入options配置对象:
也就是el, data之类的.

import  initMixin  from './init'

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) // 该处this指向Vue构造函数
  // _init确实定义于构造函数Vue上, 只不过并非现在而是在构造函数Vue带着参数传入initMixin之后


initMixin(Vue) // 构造函数Vue与initMinxin建立联系以将option传入initMixin

2.init.js

src/core/instance/init.js

initMixin()所在文件, 先看options传值, initMixin接受构造函数Vue后将_init方法定义到Vue构造函数原型上, 并接受参数.

export function initMixin (Vue: Class<Component>) 
  Vue.prototype._init = function (options?: Object) 
    // ...函数体
  

将外层initMixin剥离:

Vue.prototype._init = function (options?: Object) 
  const vm: Component = this // vm: Component为ts类型注释写法, 忽略看作const声明变量即可.
  // vm: ViewModel, vm = new Vue(??)
  // this指向Vue.prototype即本作用域对应函数
  vm._uid = uid++
  // 每个ViewModel都有一个自己的uid编号

  if (options && options._isComponent) 
    //如果options存在且存在于一个子组件中
    initInternalComponent(vm, options) // 支线
   else 
    // 如果配置对象存在且存在于根组件中
    vm.$options = mergeOptions( // 支线
      resolveConstructorOptions(vm.constructor), // 支线
      options || ,
      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')

最后这几个函数的调用次序问题, 现在先记一下initInjectionsinitState以及initProvide:
provide注入的值作为data, props, computed等的入口, provide注入, inject负责接收, 而接受到的依赖(比如axios), 假设被inject接收到之后在methodsaxios.post(xxx), 这时候要是还没有methods这个东西就不好了, 所以initState必须在initinject之前进行, 而接收机制应该在发送之前就做好, 就像天然气建好管路后才能开闸.
我会在第三章说到这些.


1.2.1.vmoptions

this(即vm):

options, 本处指传入_init``的options`:

options中有诸多属性, new Vue()传入的配置只是更改了其中一部分属性的值.


二、支线

2.1.initInternalComponent

一堆赋值操作

参数:
vm: Vue.prototype
options: _init()参数options

完整函数:

export function initInternalComponent (vm: Component, options: InternalComponentOptions) 
  const opts = vm.$options = Object.create(vm.constructor.options) // opts
  // 将以Vue构造函数对象作为原型生成的对象挂载到组件实例vm.$options的__proto__
  const parentVnode = options._parentVnode
  opts.parent = options.parent // 组件根实例挂载到组件实例
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render)  // 如果有render, 把render相关挂载到$options
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  

指定组件$options原型, 把组件依赖于父组件的propslisteners挂载到options上,方便子组件调用.


2.resolveConstructorOptions

函数全貌, Ctorvm.constrator即Vue构造函数.

export function resolveConstructorOptions (Ctor: Class<Component>) 
  let options = Ctor.options // `vm.constructor = Vue.prototype.constructor = Vue`
  if (Ctor.super)  // 有super属性,说明Ctor是Vue.extend构建的子类, 进入递归
  // Vue.extend构建的子类为何会有super属性见下
    const superOptions = resolveConstructorOptions(Ctor.super) // sub
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) 
      Ctor.superOptions = superOptions
      const modifiedOptions = resolveModifiedOptions(Ctor)
      if (modifiedOptions) 
        extend(Ctor.extendOptions, modifiedOptions)
      
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) 
        options.components[options.name] = Ctor
      
    
  
  return options // 如果Ctor.super不存在就直接返回options, 比如new Vue()创建的情况


2.1.1.什么情况下会有super

可见当Ctor.super存在时才会进行resolveConstructorOptions的递归操作, 而使用new extend()创建组件的时候vm.constructor才会有super.
new extend()使用基础Vue构造器创建子类, 参数是一个组件配置对象.

src/core/global-api/extend.js

完整函数:

Vue.extend = function (extendOptions: Object): Function 
  extendOptions = extendOptions || 
  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)
  

  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

  if (Sub.options.props) 
    initProps(Sub)
  
  if (Sub.options.computed) 
    initComputed(Sub)
  
    
  Sub.extend = Super.extend
  Sub.mixin = Super.mixin
  Sub.use = Super.use

  ASSET_TYPES.forEach(function (type) 
    Sub[type] = Super[type]
  )

  if (name) 
    Sub.options.components[name] = Sub
  

  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend(, Sub.options)

  // cache constructor
  cachedCtors[SuperId] = Sub
  return Sub

function initProps (Comp) 
  const props = Comp.options.props
  for (const key in props) 
    proxy(Comp.prototype, `_props`, key)
  


function initComputed (Comp) 
  const computed = Comp.options.computed
  for (const key in computed) 
    defineComputed(Comp.prototype, key, computed[key])
  

Sub['super'] = Super定义super属性, 而此时this指向Vue构造函数.

Vue.extend = function (extendOptions: Object): Function 
  extendOptions = extendOptions || 
  const Super = this
  console.log(this)
  console.log(Vue)


2.1.2.构造函数Sub

仍旧src/core/global-api/extend.js
简化版:

Vue.extend = function (extendOptions: Object): Function 
  extendOptions = extendOptions ||  // vm.constructor
  const Super = this // 将Super指向构造函数Vue, 为后面建Sub子类准备资源
  
  const Sub = function VueComponent (options)  // 组件构造函数Sub
    this._init(options)
  
  Sub.prototype = Object.create(Super.prototype)
  Sub.prototype.constructor = Sub
  Sub.options = mergeOptions( // 记住这个Sub.options
    Super.options,
    extendOptions
  )
  Sub['super'] = Super
  Sub.extend = Super.extend
  Sub.mixin = Super.mixin
  Sub.use = Super.use
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions // 用作子类变化对照组
  Sub.sealedOptions = extend(, Sub.options)
  return Sub

构造函数Sub我抽出来写了一下, _init()并不像new Vue()那样可以在initMixin里定义, 会报undefined, 而且这个Sub现在什么都没有就是个空函数一样的东西.
所以Sub构造函数定义之后的下一步立马就是把Super的原型, 也就是Vue.prototype拿过来当自己的prototype用, Object.create()基于Vue构造函数的原型创建一个对象, 作为组件构造函数Sub的原型, 这样Sub就是一个Vue构造函数复制品, 其内部也会拥有_init()方法可以进入initMixin()执行从而到达Ctor.super判定.
Sub.prototype刚赋值完和Vue构造函数一模一样, constructor要指向Sub构造函数才好, 所以接着把Sub.prototype指向Sub构造函数.
之后就是往Sub上挂载属性了.


2.1.3.super存在, resolveConstructorOptions如何执行

Ctor.super初始为构造函数Vue, 从最开始首次调用的父类构造函数Vue开始, 每轮递归都把父类最新的options赋值给superOptions.

演示: Vue.extend()情况下的输出, new Vue()情况下判定无法通过, 不会发生以下.
Vue.extend()vm.constructor在开始_init之前已经在extend.js中处理过, 所以它的super属性会是自己的父类.

let options = Ctor.options
console.log(Ctor.super)
if (Ctor.super) 
  const superOptions = resolveConstructorOptions(Ctor.super) // 调自己, 返回父类的options
  const cachedSuperOptions = Ctor.superOptions // 将extend时父类的options赋值给缓存
  console.log(superOptions)
  console.log(cachedSuperOptions)


这是一个父子类都没有变化的情况. 所以superOptionscachedSuperOptions相同.


Vue.extend()使用Vue构造器创建一个子类, 这个子类有一大部分基于构造函数Vue这个父类.
这其中牵扯到一个比较, 先用Ctor.super拿到父构造函数传入resolveConstructorOptions拿到父类的options存下来, 然后再Ctor.superOptions直接将自己的options存下来, 然后父子options比较, 如果不同的话就是父类的options发生了改变, 由于extend里说sub的时候说到子类大部基于父类所以子类需要伴随父类的更新而做出更新.
子类直接被赋值更新, 完成之后使用resolveModifiedOptions检测子类是否发生变化.

export function resolveConstructorOptions (Ctor: Class<Component>) 
  let options = Ctor.options
  if (Ctor.super)  // 继续递归的前提是有super, 没有就直接返回.
    const superOptions = resolveConstructorOptions(Ctor.super) // 传入Ctor.Super即父类构造函数拿到父options
    const cachedSuperOptions = Ctor.superOptions // 将自身的options存下
    if (superOptions !== cachedSuperOptions)  // 如果superOptions不等于最新的superOptions
      Ctor.superOptions = superOptions 
      // 那么更新子类, 更新完super必存在必定还要递归一次所以不用担心构造函数的问题

      const modifiedOptions = resolveModifiedOptions(Ctor) // 父子类变动校验

      if (modifiedOptions)  
      // 如果resolveModifiedOptions返回正常值, 那么将子类对照与Ctor.extendOptions与存异项合并
        extend(Ctor.extendOptions, modifiedOptions)
      
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) // 新版子类与子类对照合并
      if (options.name) 
        options.components[options.name] = Ctor
      
    
  
  return options

递归演示, 本处递归由resolveConstructorOptionssrc/sore/vdom/create-component的调用完成, 执行Vue.extend()会对该文件内createComponent调用进而调用resolveConstructorOptions, 每次调用返回一个虚拟节点, 本处v-for生成3个子组件加一个父组件共执行4次resolveConstructorOptions, 每次返回两个不同的Ctor.options, 分别为父类构造函数Vue和自身:

var todoItem = Vue.extend(
  template: `<li> text </li>`,
  props: ['text']
)

// 构建一个父组件
var todo = Vue.extend(
  template: `
    <ul>
      <todo-item v-for="(item, index) in todoData" :keys="index" v-text="item.text"></todo-item>
    </ul>
  `,
  props: ['todoData'],
  // 局部注册子组件
  components: 
    todoItem: todoItem
  
)

Vue.component('todo', todo)

new Vue(
  el: '#app',
  data: 
    todoList: [
       id: 0, text: 'text1' ,
       id: 1, text: 'text2' ,
       id: 2, text: 'text3' 
    ]
  
)

输出情况:

export function resolveConstructorOptions(Ctor: Class<Component>) 
  let options = Ctor.options
  console.log(Ctor.options) // 101行
  if (Ctor.super) 

执行结果:
父组件:

子组件(输出3次每次相同):


2.2.resolveModifiedOptions

一个校验方法, 校验双方相同时则不执行extend(Ctor.extendOptions, modifiedOptions)

function resolveModifiedOptions (Ctor: Class<Component>): ?Object 
  let modified
  const latest = Ctor.options // 自己, 或者说子类的options
  const sealed = Ctor.sealedOptions // Ctor.sealedOptions是执行extends.js时封的options, 用来对照子类自身变化
  for (const key in latest) 
    if (latest[key] !== sealed[key])  
      if (!modified) modified =  // 他这个modifined在完全相同的时候是个`undefined`, 不同的时候会用对象形式列出第一个不同的项
      modified[key] = latest[key]
    
  
  return modified

测试demo:

function resolveModifiedOptions() 
  let modified
  const latest = 
    a: 2,
    b: 3,
    c: 8
   // 自己, 或者说子类的options
  const sealed = 
    a: 1,
    b: 4,
    c: 3
   // 执行extends时封的options, 没有变化过, 专门用来对照子类自身变化
  for (const key in latest) 
    if (latest[key] !== sealed[key]) 
      if (!modified) 
        if (!modified) modified = 
          modified[key] = latest[key]
       
     
   
   console.log(modified)
   return modified

resolveModifiedOptions()

时刻同步父子类的变化, 以确保vm内的变动能够实时得到反馈.


2.3.mergeOptions

合并两选项, 出现相同配置项, 那么子类中的选项会覆盖父类中的选项.
子类最初直接源自Vue构造函数对象, 它需要尽可能发展自己的特色, 能用自己的就用自己的.

参数:
parent: 父类构造函数对象
child: 子类构造函数对象
vm: (可选)组件实例

完整函数:

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object 

  normalizeProps(child, vm) // 标准化props
  normalizeInject(child, vm) // 标准化inject
  normalizeDirectives(child) // 标准化directive

  if (!child._base) 
    if (child.extends)  //处理子类对象上的extends, 将继承而来的选项合并到parent
      parent = mergeOptions(parent, child.extends, vm)
    
    if (child.mixins)  //处理子类对象上的mixins, 将继承而来的选项合并到parent
      for (let i = 0, l = child.mixins.length; i < l; i++) 
        parent = mergeOptions(parent, child.mixins[i], vm)
      
    
  

  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 // strats受引入的合并策略集
    // strat函数, 履行特定合并合并策略的合并函数
    options[key] = strat(parent[key], child[key], vm, key以上是关于Vue 2.6.13 源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Vue 2.6.13 源码解析

Vue 2.6.13 源码解析

Vue 2.6.13 源码分析

Vue 2.6.13 源码解析 ObserverDepWatcher与订阅

Vue 2.6.13 源码解析 感知变化以发起通知

Vue 2.6.13 源码解析 initLifeCycle && initEvents