二、Vue的初始化过程

Vue

# Vue的初始化过程

从入口文件里面导入的Vue可以看出其构造函数的文件是web/runtime/index.js

# web/runtime/index.js

这里的代码都是与平台相关的.注册了全局的组件和指令,注册了$mount

# 代码结构

  • 导入了一些成员
    • 导入了环境判断inBrowser
    • 导入平台相关的指令platformDirectives和组件platformComponents
  • 给Vue.config中注册了一些方法,这些方法是与平台相关的特定的通用的方法
  • 通过extend方法注册了与平台相关的全局的指令和组件
    • 注册指令
    • 注册组件
  • 在Vue的原型对象上注册了一个patch函数
  • 给vue实例增加了一个$mount的方法
  • 与调试相关的代码,不关心
  • 导出Vue

这个文件依然没有找到Vue的构造函数,我们可以看到其 Vue在core/index文件夹中.

# core/index.js

在文件core/index.js中,给Vue初始化静态方法,注册与ssr相关的代码,版本

# 代码结构

  • 导入成员
  • 给Vue的构造函数增加一些静态方法initGlobalAPI(重点看,选中按F12,在静态成员里面有详细描述)
  • 通过Object.defineProperty注册了一些成员,这些都是ssr,与服务端渲染相关的.暂时忽略
  • Vue的版本
  • 导出Vue

这个文件依然没有找到Vue的构造函数,我们可以看到其 Vue在instance/index.js文件中, 下面的 core/instance/index.js

# core/instance/index.js

这里面才是真正的Vue构造函数的定义,创建了Vue的构造函数,设置了Vue实例的成员

# 代码结构

  • 导入成员
  • 定义Vue构造函数,接收一个参数options
    • 判断开发环境下this若不是Vue的实例,说明没有用new Vue去调用构造函数,会报出警告
    • 调用 _init() 方法
  • 混入 vm 的 _init()方法, 初始化vm
  • 混入 vm 的 $data/$props/$set/$delete/$watch 方法,这里是初始化Vue实例的一些方法和属性
  • 初始化事件相关方法
  • 初始化声明周期相关的混入方法
  • 混入 render

为什么不用类创建而用构造函数创建Vue? 此处不用 class 的原因是因为方便后续给Vue实例混入实例成员,如果用了class再用原型,很不搭.

里面把每个功能模块拆分的很细,之后我们要看哪个功能模块,直接看对应的模块即可.

笔记总结

四个导出 Vue 的模块

  • src/platforms/web/entry-runtime-with-compiler.js
    • 主要增加了编译的功能
    • web 平台相关的入口
    • 重写了平台相关的 $mount() 方法, 让其内部可以编译模板,将template转换成render函数
    • 注册了 Vue.compile() 方法,传递一个 HTML 字符串返回 render 函数
  • src/platforms/web/runtime/index.js
    • web 平台相关
    • 注册和平台相关的全局指令:v-model、v-show
    • 注册和平台相关的全局组件: v-transition、v-transition-group
    • 全局方法:
      • __patch__:把虚拟 DOM 转换成真实 DOM
      • $mount:挂载方法
  • src/core/index.js
    • 与平台无关
    • 设置了 Vue 的静态方法,调用了initGlobalAPI(Vue)
  • src/core/instance/index.js
    • 与平台无关
    • 与实例相关
    • 定义了构造函数,调用了 this._init(options) 方法
    • 给 Vue 中混入了常用的实例成员

# 初始化之静态成员

重点看initGlobalAPI方法的内部实现,下面的 core/global-api/index.js

# core/global-api/index.js

core/global-api/index.js ,给Vue的构造函数初始化静态方法

# 代码结构

  • 导入成员
  • 定义并导出了一个initGlobalAPI函数
    • 初始化Vue.config对象
    • Vue.util里面增加了一些方法
    • 静态方法set/delete/nextTick的定义
    • 静态方法observable,这个方法是让对象变成可响应的,设置响应式数据
    • 初始化Vue.options中的一些属性
      • components\directives\filters
      • _base
      • keep-alive
    • 初始化了其他的方法
      • 注册插件Vue.use()
      • 混入Vue.mixin()
      • 返回组件构造函数Vue.extend()
      • Vue.directive()\Vue.component()\Vue.filter()

# 初始化Vue.config对象

// 初始化Vue.config静态成员,定义了config属性的描述符
  const configDef = {}
  // 定义get方法, 获取config
  configDef.get = () => config
  // 如果是开发环境,给config设置值的时候会触发set方法,触发警告不要给Vue.config重新赋值,可以在上面挂载方法
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  // 利用Object.defineProperty定义Vue.config属性
  Object.defineProperty(Vue, 'config', configDef)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Vue.util增加方法

这些方法没有在文档中写,也不建议我们使用,这里不做说明

// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
// Vue.util里面增加了一些方法
// 上面给了NOTE:这些方法不能被视作全局API的一部分,除非你已经意识到某些风险,否则不要去依赖他们
// 意思是我们在调用这些方法的时候可能会出现意外,所以要避免去调用这些方法,vue这么做是其内部要进行使用
Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
}
1
2
3
4
5
6
7
8
9
10
11
12

# 静态方法set/delete/nextTick

分析完响应式再来看

Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
1
2
3

# 静态方法Vue.observable

这个方法是让对象变成可响应的,设置响应式数据

// 静态方法observable
Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
}
1
2
3
4
5

# 初始化Vue.options中的属性

// 先设置一个空对象,创建改对象的同时设置原型等于null,说明当前不需要原型,可以提高性能
Vue.options = Object.create(null)
/**
* 引用自 shared/constants.js
* export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
* 遍历数组的名字加s之后挂载到Vue.options下面,这三个属性对应的值都是一个空对象,它们的作用分别是存储全局的组件\指令和过滤器
* 我们通过Vue.component,Vue.directive,Vue.filter注册的全局组件\指令和过滤器都会存储到Vue.options对应的属性上
*/
ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# _base记录当前构造函数

Vue.options._base = Vue
1

# 注册全局组件keep-alive

/**
* 注册全局组件keep-alive
* extend
* 在shared/util.js目录下,extend是实现了浅拷贝,第二个参数的成员拷贝给了第一个参数的成员
* export function extend (to: Object, _from: ?Object): Object {
      for (const key in _from) {
        to[key] = _from[key]
      }
      return to
  }

builtInComponents
名字可以看出来这是一个内置组件,在core/components/index.js目录下,导出的组件其实是KeepAlive
所以在还注册了内置组件keep-alive
*/
extend(Vue.options.components, builtInComponents)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 定义 Vue.use 静态成员

// 注册 Vue.use() 用来注册插件
initUse(Vue)
1
2

这个方法的定义在同级的use.js中

// 定义并导出函数initUse,传入参数是Vue构造函数
export function initUse (Vue: GlobalAPI) {
  // 定义了Vue.use的方法,接收了一个参数plugin(插件,可以是函数也可以是对象)
  Vue.use = function (plugin: Function | Object) {
    // 定义了一个常量,installedPlugins表示已经安装的插件
    // 这个地方的this只是的Vue的构造函数
    // 获取_installedPlugins属性如果有就返回,如果没有就初始化成一个空数组,这个里面记录了安装的插件.
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    
    // 如果注册的插件已经存在,则直接返回,如果没有注册,下面注册插件,注册插件其实就是调用传入的插件
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

  // 这里整体的功能是调用插件的方法并传递相应的参数
  
    // additional parameters
    // 这里对调用此方法的其余参数做处理
    // use方法可以传一个参数,也可以传多个参数,如果传多个参数,那么第一个是插件,其余的都是调用时候传入的参数
    // toArray方法将arguments转换成数组,后面的1是把第一个参数去掉
    const args = toArray(arguments, 1)

    // 将Vue构造函数插入到参数数组的第一项中
    args.unshift(this)

    // 如果plugin.install有值的话说明plugin是一个对象,直接调用其install方法
    // 文档中说,如果要注册一个插件,其中必须要有一个install方法,这是对插件的要求
    if (typeof plugin.install === 'function') {
      // 使用apply方法改变其内部的this,第一个传plugin是plugin调用的方法,apply方法会将args数组展开,第一个参数是Vue,intall方法要求的第一个参数也是Vue
      plugin.install.apply(plugin, args)
    // 如果是函数那么直接调用这个函数
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }

    // 当注册好插件之后要将插件保存到已安装的插件数组中
    installedPlugins.push(plugin)
    
    // 返回Vue的构造函数
    return this
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 定义 Vue.mixin 静态成员

// 注册 Vue.mixin() 实现混入
initMixin(Vue)
1
2

这个方法的定义在同级的mixin.js中

export function initMixin (Vue: GlobalAPI) {
  // 传入参数
  Vue.mixin = function (mixin: Object) {
    // 这里的this指的是Vue构造函数
    // 把mixin对象的成员拷贝到Vue.options中,所以mixin注册的是全局选项,官网上有文档
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}
1
2
3
4
5
6
7
8
9

# 定义 Vue.extend 静态成员

返回一个组件的构造函数,做自定义组件的话可能会用到

// 注册 Vue.extend() 基于传入的options返回一个组件的构造函数
initExtend(Vue)
1
2

这个方法的定义在同级的extend.js中,下面对一些核心代码进行注解

export function initExtend (Vue: GlobalAPI) {
  ...
  /**
   * Class inheritance
   * 参数是选项,对象
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    // Super是this是Vue构造函数
    const Super = this
    const SuperId = Super.cid
    ...

    // 核心代码
    // 创建一个构造函数 VueComponent ,组件对应的构造函数
    const Sub = function VueComponent (options) {
      // 调用_init()初始化
      this._init(options)
    }
    // 改变了构造函数的原型,让其继承自Vue,故所有的组件都继承自Vue
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    
    // 合并 options 选项
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // 把Super中的成员拷贝到VueComponent构造函数中来
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    // 注册Vue.component\Vue.filter\Vue.directive方法
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    ...
    // 最后返回组件的构造函数VueComponent
    return Sub
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 定义 Vue.directive 等静态成员

Vue.directive()\Vue.component()\Vue.filter()这三个方法不是一个一个注册的,是一起注册的,因为其参数是一样的.

Vue文档的全局API中,第一个传id,第二个传定义(可以是对象也可以是函数)

// 注册 Vue.directive()\Vue.component()\Vue.filter()
initAssetRegisters(Vue)
1
2

这个方法的定义在同级的assets.js中

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   * 没有直接定义而是遍历数组,里面其实就是'component','directive','filter'
   */
  ASSET_TYPES.forEach(type => {
    // 给每个值分别设置一个方法
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      // 没有传定义的话会找到之前options中定义的方法
      // id就是组件名称 or 指令名称
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        ...
        /**
         * 判断类型是否是组件,且定义是否是原始的Object,判断其转换成字符串是不是'[object Object]'
         */
        if (type === 'component' && isPlainObject(definition)) {
        
          // 如果设置了组件名称name就用name,如果没有就用id作为组件名称
          definition.name = definition.name || id
          
          // this.options._base就是Vue构造函数
          // Vue.extend()就是把普通对象转换成了VueComponent构造函数
          // 官方文档中也可以直接传一个Vue.extend构造函数,如果第二个参数definition是Vue.extend的构造函数,那么就直接执行return的最后一句话
          definition = this.options._base.extend(definition)
        }
        
        // 如果是指令,且是函数的话,会把定义赋值给bind和update两个方法
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        
        // 所有的内容处理之后够会直接挂载到this.options下面去,通过这个注册的是全局的组件\指令等
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 初始化之实例成员

(笔记对应上面core/instance/index.js)

core/instance/index.js

这个里面定义了Vue构造函数,还初始化了很多实例成员,下面具体分析

这几个函数的名字有特点,后面都有Mixin,都传入了Vue构造函数,这几个方法类似的作用就是给Vue的原型对象上混入了相应的成员,也就是给Vue的实例上增加了相应的成员.

# initMixmin

混入初始化方法 _init()

同级init.js中

// 初始化了Vue实例的成员,并且触发了beforeCreate和created钩子函数,触发$mount渲染整个页面
// 不同的初始化在不同的模块中,结构很清晰,这些代码不需要记住,用到哪些成员回头看即可

export function initMixin (Vue: Class<Component>) {
  // 给 Vue 实例增加 _init()方法
  Vue.prototype._init = function (options?: Object) {
    // 定义一个vm常量指代Vue的实例
    const vm: Component = this
    // 定义uid,唯一标识
    vm._uid = uid++

    // 开发环境下的性能检测,略过
    let startTag, endTag
    ...

    // a flag to avoid this being observed
    // 标识当前实例是Vue实例,之后做响应式数据的时候不对其进行处理
    vm._isVue = true
    
    // merge options
    // 合并options,这两个相似的是把用户传入的options和构造函数中的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 */
    // 渲染时候的代理对象,实际设置成了Vue实例
    // 在渲染过程的时候会看到这个属性的使用
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    
    // expose real self
    vm._self = vm
    
    // 初始化的函数
    // 初始化和生命周期相关的内容
    // $children/$parent/$root/$refs
    initLifecycle(vm)
    // 初始化当前组件的事件
    initEvents(vm)
    // 初始化render中所使用的h函数,初始化了几个属性$slots/$scopedSlots/_c/$createElement/$attrs/$listeners
    initRender(vm)
    // 触发声明周期的钩子函数beforeCreate
    callHook(vm, 'beforeCreate')
    // initInjections与initProvide是一对,实现依赖注入
    initInjections(vm) // resolve injections before data/props
    // 初始化 vm 的 _props/methods/_data/computed/watch
    initState(vm)
    // initProvide函数中,会把父组件提供的成员存储到_provided里面中
    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)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# initProxy

渲染时候的代理对象,实际设置成了Vue实例

initProxy = function initProxy (vm) {
    // 首先判断该环境下是否有proxy对象
    // 如果有代理对象就用new Proxy初始化,否则就把_renderProxy设置成Vue实例
    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
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# initLifecycle

export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  // 找到当前Vue实例的父组件,将其添加到父组件的$children里面去
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
  // 挂载$parent\$root\$children\$refs
  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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# initEvents

export function initEvents (vm: Component) {
  // 给vm添加了属性_events,这个属性是用来存储事件名称以及对应的处理函数,键就是事件名称,值就是事件处理函数
  // 值是数组形式,因为一个事件名称对应多个事件处理函数,发布订阅模式中曾经模拟过这种形式
  // 在下面的$on中可以看到其使用
  // (vm._events[event] || (vm._events[event] = [])).push(fn)
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  // 获取父元素上附加的事件,注册到当前组件中
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# initRender

export function initRender (vm: Component) {
  ...
  // 初始化$slots/$scopedSlots两个去插槽相关的属性
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject

  // 重点方法 _c/$createElement
  // 对手动传入template属性,其编译生成的 render 进行渲染的方法,其调用的也是createElement,最后一个参数不一样
  // 当把template编译成render函数的时候,其内部会调用_c
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  
  // 对手动传入的 render 函数进行渲染的方法
  // $createElement就是new Vue的时候传入render(h)的h函数,所用是把虚拟DOM转换成真实DOM
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  ...
  // 定义$attrs和$listeners属性,用defineReactive是定义响应式数据,这些数据是只读的,开发环境不允许赋值
  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)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# initInjections

// 依赖注入的实现原理
export function initInjections (vm: Component) {
  // 将inject对象的所有属性,判断这些属性如果在vm._provided属性中存在就提取出来放到result
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    // 遍历属性将其注入到Vue实例并设置成响应式数据
    Object.keys(result).forEach(key => {
      
      /* istanbul ignore else */
      // 生产环境下如果直接给inject环境赋值会发送警告
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

其中resolveInject函数的如何实现的?

export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    ...
    // 核心代码,重点看
    // 先拿到inject里面所有的keys
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)
    // 这些keys是inject中的所有属性,判断keys是否在source._provided里面,source就是vm实例
    for (let i = 0; i < keys.length; i++) {
      ...
      let source = vm
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          // 如果这个属性在source._provided中,就放到result里面
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      ...
    }
    // 最终返回result
    return result
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# initState

export function initState (vm: Component) {
  vm._watchers = []
  // 获取了实例中的$options
  // 判断props,methods,data,computed,watch这些属性,如果有就用init进行初始化
  const opts = vm.$options
  // initProps:把props数据转换成响应式数据并且注入到Vue实例中
  if (opts.props) initProps(vm, opts.props)
  // initMethods:初始化了选项中的methods,在注入之前判断了方法名称和值
  if (opts.methods) initMethods(vm, opts.methods)
  // 如果参数中有data就执行initData
  // 如果参数中没有data就在vm初始化一个_data并赋值一个空对象,并且进行响应式处理
  if (opts.data) {
    //initData:初始化选项中的data,注入到Vue实例中,注入之前进行重名判断.并且将data进行响应式处理
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始化计算属性和侦听器,注入到Vue实例中
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# initProps
function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  // 定义了一个_props对象并存到props常量中
  const props = vm._props = {}
  const keys = vm.$options._propKeys = []
  ...
  // 遍历propsOptions(vm.$options.props)的所有属性,将属性都通过defineReactive转换成 get\set 注入到props(vm._props)里面
  // 所有的成员都会再_props里面存储
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      ...
    } else {
      defineReactive(props, key, value)
    }
    
    // vm.$options.props判断是否在Vue实例中存在,如果不存在通过proxy方法把属性注入到Vue实例中
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# initMethods
function initMethods (vm: Component, methods: Object) {
  // 获取选项中的$options.props,这里为什么要获取props是为了下面名称重复排查需要
  const props = vm.$options.props
  
  // 遍历methods中的所有属性(方法名称)
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      // 如果是开发环境判断methods值是否是function,如果不是function就会发送警告
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      
      // 当前方法名称是否在props中存在,会警告此名称已经在props中存在,因为最终props和methods都要注入到Vue的实例上,所以他们不能有同名存在.
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      
      // 判断方法名称是否在vue中存在,并且判断该名称是否以_或者$开头
      // 如果以下划线开头,那Vue认为这是一个私有属性,不建议这样命名
      // 如果以$开头,公认为Vue提供的成员,也不建议这样命名
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    
    // 最后将methods值注入到Vue实例中来,先判断这个值是否是function,如果不是就直接返回一个noop空函数,如果是就返回该函数的bind方法,bind方法是改变this指向
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# initData
function initData (vm: Component) {
  // 获取options中的data选项
  let data = vm.$options.data
  // 判断data是否是function,如果是就调用getData
  // 当组件中初始化data的时候会设置成一个函数
  // 如果是Vue实例中的data是一个对象,并没有传入就初始化一个空对象
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  // 接下来获取了data中的所有属性,还获取了props和methods的所有属性,目的也是判断data中的成员是否与props和methods重名.如果有就发送警告.
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 判断data的属性是否以_或者$开头,如果是不会注入到Vue的实例中
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    // 如果不是_或者$开头,就会把属性注入到Vue实例中,
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 把data转化成响应式对象
  observe(data, true /* asRootData */)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# initComputed

没有细讲,原理一样

# initWatch

没有细讲,原理一样

# initProvide

export function initProvide (vm: Component) {
  // 找到$options.provide对象(也可能是函数),将这个成员存储到vm._provided中(如果是函数,就调用改变其this,如果是对象直接存储)
  // 这个属性在initInject中会使用到
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}
1
2
3
4
5
6
7
8
9
10

# stateMixin

混入 $data/$props/$set/$delete/$watch 方法

同级state.js中

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  // 定义描述符,get方法返回实例对象的_data,_props
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  // 如果是开发环境下,不允许给$data和$props赋值
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  
  // 通过Object.defineProperty给原型上添加$data,$props
  // 后面是两个对象的描述符,上面给描述符定义了get方法
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  // 在原型是挂载了$set和$delete,这个与Vue.set\Vue.delete是一模一样的
  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  //原型上挂载了$watch,监视数据的变化
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# eventsMixin

混入事件相关方法 $on$once$off$emit,这里的事件使用的是发布订阅模式

同级events.js

export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  // 注册事件
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    // 判断事件是否是数组,如果是数组就遍历数组给多个事件注册处理函数
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    // 如果是字符串就调用_events,_events是一个对象,之前有定义 vm._events = Object.create(null)
    } else {
      // 对象属性就是事件的名称,根据对象属性找事件存储的内容,如果没有找到就初始化成数组
      // 最终的结果将处理函数添加到数组中
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      ...
    }
    return vm
  }

  // 注册事件(一次性)
  Vue.prototype.$once = function (event: string, fn: Function): Component {
    ...
  }
  // 注销事件
  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    ...
  }
  // 触发事件
  Vue.prototype.$emit = function (event: string): Component {
    ...
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# lifecycleMixin

混入声明周期相关的方法 _update/$forceUpdate/$destory

同级lifecycle.js

export function lifecycleMixin (Vue: Class<Component>) {
  // 更新,最最核心的是里面调用了patch
  // patch的作用是把虚拟DOM转换成真实DOM挂载到$el中
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    ...
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    // 判断是不是第一次调用,如果是就调用上面的__patch__方法,如果不是就调用下面的__patch__方法
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    ...
  }

  // 强制更新,会调用_watcher的update
  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }
  // 销毁Vue实例
  Vue.prototype.$destroy = function () {
    ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# renderMixin

混入 _render, $nextTick方法,作用是调用用户传入的render

同级render.js

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // 安装了渲染相关的一些帮助方法,将Vue原型传入
  installRenderHelpers(Vue.prototype)

  // 挂载$nextTick方法
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  // 挂载_render方法
  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    // 从options中获取了render,这个render是用户定义的render,或者是模板渲染的render
    const { render, _parentVnode } = vm.$options

    ...
    let vnode
    try {
      ...
      // 核心,在_render中调用了传入的渲染函数
      // 通过call方法调用,第一个方法是改变其内部的this
      // render(h),这个$createElement就是h,生成虚拟DOM
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      ...
    } finally {
      ...
    }
    ...
    return vnode
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
更新时间: 2022-01-26 22:04