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: VueModel
    // this指向Vue.prototype即本作用域对应函数
    vm._uid = uid++
    // 每个VueModel都有一个自己的uid编号

    if (options && options._isComponent) 
      // 如果配置对象存在且存在于子组件中
      initInternalComponent(vm, options) // 支线
     else 
      // 如果配置对象存在且存在于根组件中
      vm.$options = mergeOptions( // 支线
        resolveConstructorOptions(vm.constructor), // 支线
        options || ,
        vm
      )
    

    // expose real self
    vm._self = vm
    

    if (vm.$options.el) 
      // 如果配置对象内存在el属性,则调用$mount方法, 即配置对象内存在el属性就不需要手动调用 $mount
      vm.$mount(vm.$options.el)
    
  

二、支线

1.initInternalComponent

一堆赋值操作

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

完整函数:

export function initInternalComponent (vm: Component, options: InternalComponentOptions) 
  const opts = vm.$options = Object.create(vm.constructor.options)
  // 将以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原型, 把组件依赖于父组件的props、listeners挂载到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.Vue, this, Vue.prototype.constructor分别是什么?

const options = 
  el: '#app',
  data: 
    msg: 'hello vue'
  


function Vue(options) 
  this._init(options)


Vue.prototype._init = function () 
  const vm = this
  console.log(Vue)
  console.log(this)
  console.log(Vue.prototype.constructor)


new Vue()


2.2.什么情况下会有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

简化:

const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = )
if (cachedCtors[SuperId]) 
  return cachedCtors[SuperId]

const Sub = function VueComponent (options) 
  this._init(options)

Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub['super'] = Super
return Sub

Sub['super'] = Super定义super属性, 值为父类构造函数Vue.


2.3.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.4.super存在, resolveConstructorOptions如何执行

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

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


3.resolveModifiedOptions

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

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

测试:

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

4.mergeOptions

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

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

完整函数:

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
  

  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)
  
  return options


总结

以上是关于Vue 2.6.13 源码分析的主要内容,如果未能解决你的问题,请参考以下文章

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 源码解析 initLifeCycle && initEvents