vue 源码详解(二): 组件生命周期初始化、事件系统初始化

上一篇文章 生成 Vue 实例前的准备工作 讲解了实例化前的准备工作, 接下来我们继续看, 我们调用 new Vue() 的时候, 其内部做了哪些工作。

1. 从 Vue 构造函数开始

new Vue(options) 时, Vue 构造函数中只有一句代码 this._init(options) 。 通过执行这个函数顺次调用了下边代码中注释处 1 ~ 10 的代码, 下面就按照代码的执行顺序,依次解释下每个函数的作用。

  1. let uid = 0
  2. export function initMixin (Vue: Class<Component>) {
  3. Vue.prototype._init = function (options?: Object) {
  4. const vm: Component = this
  5. // a uid
  6. vm._uid = uid++ // 1
  7. let startTag, endTag
  8. /* istanbul ignore if */
  9. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  10. startTag = `vue-perf-start:${vm._uid}`
  11. endTag = `vue-perf-end:${vm._uid}`
  12. mark(startTag)
  13. }
  14. // a flag to avoid this being observed
  15. vm._isVue = true
  16. // merge options
  17. if (options && options._isComponent) {
  18. // optimize internal component instantiation
  19. // since dynamic options merging is pretty slow, and none of the
  20. // internal component options needs special treatment.
  21. initInternalComponent(vm, options)
  22. } else {
  23. vm.$options = mergeOptions(
  24. resolveConstructorOptions(vm.constructor),
  25. options || {},
  26. vm
  27. )
  28. }
  29. /* istanbul ignore else */
  30. if (process.env.NODE_ENV !== 'production') {
  31. initProxy(vm)
  32. } else {
  33. vm._renderProxy = vm
  34. }
  35. // expose real self
  36. vm._self = vm
  37. initLifecycle(vm) // 2
  38. initEvents(vm) // 3
  39. initRender(vm) // 4
  40. callHook(vm, 'beforeCreate') // 5
  41. initInjections(vm) // 6 resolve injections before data/props
  42. initState(vm) // 7
  43. initProvide(vm) // 8 resolve provide after data/props
  44. callHook(vm, 'created') // 9
  45. /* istanbul ignore if */
  46. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  47. vm._name = formatComponentName(vm, false)
  48. mark(endTag)
  49. measure(`vue ${vm._name} init`, startTag, endTag)
  50. }
  51. if (vm.$options.el) {
  52. vm.$mount(vm.$options.el) // 10
  53. }
  54. }
  55. }

1.1.1 初始化声明周期

  1. 注释很明了, locate first non-abstract parent, 即找到当前组件的第一个非抽象的父组件, 作为当前组件的父组件,并将当前组件记录到父组件的 $children 列表中。组件建立父子组件关系时,抽象组件(如 keep-alive)是被忽略的。

  2. 对当前组件的一些属性进行初始化, $parent 标记当前组件的父组件。 $root 标记当前组件的根组件, 若存在父组件, 则当前组件的根组件为父组件的根组件;若不存在父组件, 则当前组件的根组件是当前组件自身。然后初始化当前组件的子组件列表、引用列表 ($children / $refs) 分别为空。然后初始化了观察者列表 _watchernull. 最后初始化了 _isMounted _isDestroyed _isBeingDestroyed 分别为 false, 依次表示 为挂载到 DOM 、 组件未销毁、 组件当前非正在销毁状态

vue/src/core/instance/lifecycle.js : initLifecycle

  1. export function initLifecycle (vm: Component) {
  2. const options = vm.$options
  3. // 1 locate first non-abstract parent
  4. let parent = options.parent
  5. if (parent && !options.abstract) {
  6. while (parent.$options.abstract && parent.$parent) {
  7. parent = parent.$parent
  8. }
  9. parent.$children.push(vm)
  10. }
  11. // 2. 对当前组件的一些属性进行初始化
  12. vm.$parent = parent
  13. vm.$root = parent ? parent.$root : vm
  14. vm.$children = []
  15. vm.$refs = {}
  16. vm._watcher = null
  17. vm._inactive = null
  18. vm._directInactive = false
  19. vm._isMounted = false
  20. vm._isDestroyed = false
  21. vm._isBeingDestroyed = false
  22. }

1.1.2 初始化事件

  1. vm._events 初始化了当前组件 vm 的事件存储对象, 默认是一个没有任何属性的空对象;
  2. 收集父组件上监听的事件对象,也就是监听器, 如果父组件上有监听器, 则和当前组件的监听器进行一系列对比,并更新。 具体逻辑详见下面 updateListeners的注释.

vue/src/core/instance/events.js :

这段代码中的 add remove 函数即我们上次提到的 eventMixin 中给 Vue 对象初始化的事件系统。

  1. export function initEvents (vm: Component) {
  2. vm._events = Object.create(null) // 1 事件存储对象
  3. vm._hasHookEvent = false
  4. // init parent attached events
  5. const listeners = vm.$options._parentListeners
  6. if (listeners) { // 2 如果父组件上有监听器, 则和当前组件的监听器进行一系列对比,并更新
  7. updateComponentListeners(vm, listeners)
  8. }
  9. }
  10. let target: any
  11. function add (event, fn) {
  12. target.$on(event, fn)
  13. }
  14. function remove (event, fn) {
  15. target.$off(event, fn)
  16. }
  17. function createOnceHandler (event, fn) {
  18. const _target = target
  19. return function onceHandler () {
  20. const res = fn.apply(null, arguments)
  21. if (res !== null) {
  22. _target.$off(event, onceHandler)
  23. }
  24. }
  25. }
  26. export function updateComponentListeners (
  27. vm: Component,
  28. listeners: Object,
  29. oldListeners: ?Object
  30. ) {
  31. target = vm
  32. updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  33. target = undefined
  34. }

vue/src/core/vdom/helpers/update-listeners.js


  1. // 这个函数用来解析一个事件信息, 返回事件的名称、和是否被 once / capture / passive 修饰符修饰过
  2. const normalizeEvent = cached((name: string): {
  3. name: string,
  4. once: boolean,
  5. capture: boolean,
  6. passive: boolean,
  7. handler?: Function,
  8. params?: Array<any>
  9. } => {
  10. const passive = name.charAt(0) === '&'
  11. name = passive ? name.slice(1) : name
  12. const once = name.charAt(0) === '~' // Prefixed last, checked first
  13. name = once ? name.slice(1) : name
  14. const capture = name.charAt(0) === '!'
  15. name = capture ? name.slice(1) : name
  16. return {
  17. name,
  18. once,
  19. capture,
  20. passive
  21. }
  22. })
  23. export function createFnInvoker (fns: Function | Array<Function>, vm: ?Component): Function {
  24. function invoker () {
  25. const fns = invoker.fns
  26. if (Array.isArray(fns)) {
  27. const cloned = fns.slice()
  28. for (let i = 0; i < cloned.length; i++) {
  29. invokeWithErrorHandling(cloned[i], null, arguments, vm, `v-on handler`)
  30. }
  31. } else {
  32. // return handler return value for single handlers
  33. return invokeWithErrorHandling(fns, null, arguments, vm, `v-on handler`)
  34. }
  35. }
  36. invoker.fns = fns
  37. return invoker
  38. }
  39. export function updateListeners (
  40. on: Object, // 父组件的事件监听对象
  41. oldOn: Object, // 如果不是初次渲染, 原先的 vm 实例上可能存在一些原有的事件
  42. add: Function,
  43. remove: Function,
  44. createOnceHandler: Function,
  45. vm: Component
  46. ) {
  47. let name, def, cur, old, event
  48. for (name in on) { // 遍历父组件的事件对象
  49. def = cur = on[name] // 父组件中的事件
  50. old = oldOn[name] // 当前组件中已存在的同名事件
  51. event = normalizeEvent(name) // 解析当前事件, 获取详细信息, 见 `normalizeEvent` 函数的注释
  52. /* istanbul ignore if */
  53. if (__WEEX__ && isPlainObject(def)) {
  54. cur = def.handler
  55. event.params = def.params
  56. }
  57. if (isUndef(cur)) { // 父组件事件不存在, 直接报警告
  58. process.env.NODE_ENV !== 'production' && warn(
  59. `Invalid handler for event "${event.name}": got ` + String(cur),
  60. vm
  61. )
  62. } else if (isUndef(old)) { // 父组件事件存在, 并且当前组件不存在该事件的同名事件
  63. if (isUndef(cur.fns)) { // 如果当前事件的事件处理函数不存在, 报错
  64. cur = on[name] = createFnInvoker(cur, vm)
  65. }
  66. if (isTrue(event.once)) { // 如果是 once 修饰符修饰过的事件
  67. cur = on[name] = createOnceHandler(event.name, cur, event.capture) // 为当前组件绑定 once 类型事件
  68. }
  69. add(event.name, cur, event.capture, event.passive, event.params) // 将事件存入当前组件事件对象
  70. } else if (cur !== old) {
  71. // 父组件存在该事件,子组件存在同名事件, 并且父子组件对于同一个事件的处理函数不相同
  72. // 则采用从父组件传递过来的处理函数
  73. old.fns = cur
  74. on[name] = old
  75. }
  76. }
  77. // 删除 vm 上之前存在、现在不存在的事件
  78. for (name in oldOn) {
  79. if (isUndef(on[name])) {
  80. event = normalizeEvent(name)
  81. remove(event.name, oldOn[name], event.capture)
  82. }
  83. }
  84. }

vue\src\shared\util.js

isUndef isDef 这两个函数,就不解释了,知道是干啥的就可以了。

  1. export function isUndef (v: any): boolean %checks {
  2. return v === undefined || v === null
  3. }
  4. export function isDef (v: any): boolean %checks {
  5. return v !== undefined && v !== null
  6. }

1.1.3 渲染初始化与数据监听

终于来到大名鼎鼎的响应式数据处理了。

  1. export function initRender (vm: Component) {
  2. vm._vnode = null // the root of the child tree
  3. vm._staticTrees = null // v-once cached trees
  4. const options = vm.$options
  5. const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  6. const renderContext = parentVnode && parentVnode.context
  7. vm.$slots = resolveSlots(options._renderChildren, renderContext)
  8. vm.$scopedSlots = emptyObject
  9. // bind the createElement fn to this instance
  10. // so that we get proper render context inside it.
  11. // args order: tag, data, children, normalizationType, alwaysNormalize
  12. // internal version is used by render functions compiled from templates
  13. vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  14. // normalization is always applied for the public version, used in
  15. // user-written render functions.
  16. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  17. // $attrs & $listeners are exposed for easier HOC creation.
  18. // they need to be reactive so that HOCs using them are always updated
  19. const parentData = parentVnode && parentVnode.data
  20. /* istanbul ignore else */
  21. if (process.env.NODE_ENV !== 'production') {
  22. defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
  23. !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
  24. }, true)
  25. defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
  26. !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
  27. }, true)
  28. } else {
  29. defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
  30. defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  31. }
  32. }

vue 源码详解(二): 组件生命周期初始化、事件系统初始化的更多相关文章

  1. Vue源码学习(二)——生命周期

    官网对生命周期给出了一个比较完成的流程图,如下所示: 从图中我们可以看到我们的Vue创建的过程要经过以下的钩子函数: beforeCreate => created => beforeMo ...

  2. vue 源码详解(一):原型对象和全局 `API`的设计

    vue 源码详解(一):原型对象和全局 API的设计 1. 从 new Vue() 开始 我们在实际的项目中使用 Vue 的时候 , 一般都是在 main.js 中通过 new Vue({el : ' ...

  3. 你还不知道Vue的生命周期吗?带你从Vue源码了解Vue2.x的生命周期(初始化阶段)

    作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e8 ...

  4. OkHttp3源码详解(二) 整体流程

    1.简单使用 同步: @Override public Response execute() throws IOException { synchronized (this) { if (execut ...

  5. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  6. Shiro 登录认证源码详解

    Shiro 登录认证源码详解 Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证.授权管理.企业级会话管理和加密等功能,相比 Spring Security 来说要更加 ...

  7. Activiti架构分析及源码详解

    目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...

  8. udhcp源码详解(五) 之DHCP包--options字段

    中间有很长一段时间没有更新udhcp源码详解的博客,主要是源码里的函数太多,不知道要不要一个一个讲下去,要知道讲DHCP的实现理论的话一篇博文也就可以大致的讲完,但实现的源码却要关心很多的问题,比如说 ...

  9. 源码详解系列(六) ------ 全面讲解druid的使用和源码

    简介 druid是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,druid还扩展 ...

随机推荐

  1. 模拟windows10计算器的实现

    用户界面部分: import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.HashMap; impo ...

  2. 17、ansible配置管理

    17.1.前言: 1.说明: ansible是自动化运维工具,基于Python开发,实现了批量系统配置.批量程序部署.批量运行命令等功能. ansible是基于模块工作的,本身没有批量部署的能力,真正 ...

  3. csu-acm 1503: 点到圆弧的距离

    1503: 点到圆弧的距离 分析: 先判断点和圆心的连线是否在圆弧范围内,如果在,最短距离即到圆心的距离减去半径的绝对值:反之,为到端点的最短距离. 具体看注释 #include <bits/s ...

  4. CRM系统如何帮助企业管理多条业务线的?

    在如今的市场环境中,许多企业为了提高销售效率,增加业绩收入,都会选择使用CRM客户关系管理系统来帮助进行对客户和销售的管理.CRM系统能够帮助企业在不同的产品线上同时开展营销活动.各个销售团队能够独立 ...

  5. PostgreSQL用户和权限问题

    PostgreSQL用户 其实用户和角色都是角色,只是用户是具有登录权限的角色. 创建用户 create user sonar password '123'; 删除用户 drop user sonar ...

  6. kafka错误集锦

    javax.management.InstanceAlreadyExistsException: kafka.consumer:type=FetchRequestAndResponseMetrics, ...

  7. mysql 的基础操作

    1.建表 create table  表名( 字段一 数据类型 [列属性] , 字段二 数据类型 [列属性], ......... )[表类型][表字符集][注释]; 注意:MySQL命令终止符为分号 ...

  8. [开源名人访谈录] Philippe Gerum

    译至:http://www.advogato.org/article/803.html 译者按:这篇采访的时间很早,但有助于你了解Xenomai相关的背景. 这是对菲利普格鲁姆,ADEOS项目的共同领 ...

  9. cron表达式详解(转)

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: (1) Seconds Minutes Hours DayofMonth Mo ...

  10. STM32中的GPIO笔记

    1.GPIO是STM32可控制的引脚,STM32的GPIO被分成很多组,每组有16个引脚.每个GPIO端口包含:2个32位配置寄存器(CRL.CRH),2个32位数据寄存器(IDR.ODR),1个32 ...