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

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

1. 从 Vue 构造函数开始

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

let uid = 0

export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++ // 1 let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
} // a flag to avoid this being observed
vm._isVue = true
// merge 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 */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm) // 2
initEvents(vm) // 3
initRender(vm) // 4
callHook(vm, 'beforeCreate') // 5
initInjections(vm) // 6 resolve injections before data/props
initState(vm) // 7
initProvide(vm) // 8 resolve provide after data/props
callHook(vm, 'created') // 9 /* 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) // 10
}
}
}

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

export function initLifecycle (vm: Component) {
const options = vm.$options // 1 locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
} // 2. 对当前组件的一些属性进行初始化
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.1.2 初始化事件

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

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

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

export function initEvents (vm: Component) {
vm._events = Object.create(null) // 1 事件存储对象
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) { // 2 如果父组件上有监听器, 则和当前组件的监听器进行一系列对比,并更新
updateComponentListeners(vm, listeners)
}
} let target: any function add (event, fn) {
target.$on(event, fn)
} function remove (event, fn) {
target.$off(event, fn)
} function createOnceHandler (event, fn) {
const _target = target
return function onceHandler () {
const res = fn.apply(null, arguments)
if (res !== null) {
_target.$off(event, onceHandler)
}
}
} export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}

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


// 这个函数用来解析一个事件信息, 返回事件的名称、和是否被 once / capture / passive 修饰符修饰过
const normalizeEvent = cached((name: string): {
name: string,
once: boolean,
capture: boolean,
passive: boolean,
handler?: Function,
params?: Array<any>
} => {
const passive = name.charAt(0) === '&'
name = passive ? name.slice(1) : name
const once = name.charAt(0) === '~' // Prefixed last, checked first
name = once ? name.slice(1) : name
const capture = name.charAt(0) === '!'
name = capture ? name.slice(1) : name
return {
name,
once,
capture,
passive
}
}) export function createFnInvoker (fns: Function | Array<Function>, vm: ?Component): Function {
function invoker () {
const fns = invoker.fns
if (Array.isArray(fns)) {
const cloned = fns.slice()
for (let i = 0; i < cloned.length; i++) {
invokeWithErrorHandling(cloned[i], null, arguments, vm, `v-on handler`)
}
} else {
// return handler return value for single handlers
return invokeWithErrorHandling(fns, null, arguments, vm, `v-on handler`)
}
}
invoker.fns = fns
return invoker
} export function updateListeners (
on: Object, // 父组件的事件监听对象
oldOn: Object, // 如果不是初次渲染, 原先的 vm 实例上可能存在一些原有的事件
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) { // 遍历父组件的事件对象
def = cur = on[name] // 父组件中的事件
old = oldOn[name] // 当前组件中已存在的同名事件
event = normalizeEvent(name) // 解析当前事件, 获取详细信息, 见 `normalizeEvent` 函数的注释
/* istanbul ignore if */
if (__WEEX__ && isPlainObject(def)) {
cur = def.handler
event.params = def.params
}
if (isUndef(cur)) { // 父组件事件不存在, 直接报警告
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) { // 父组件事件存在, 并且当前组件不存在该事件的同名事件
if (isUndef(cur.fns)) { // 如果当前事件的事件处理函数不存在, 报错
cur = on[name] = createFnInvoker(cur, vm)
}
if (isTrue(event.once)) { // 如果是 once 修饰符修饰过的事件
cur = on[name] = createOnceHandler(event.name, cur, event.capture) // 为当前组件绑定 once 类型事件
}
add(event.name, cur, event.capture, event.passive, event.params) // 将事件存入当前组件事件对象
} else if (cur !== old) {
// 父组件存在该事件,子组件存在同名事件, 并且父子组件对于同一个事件的处理函数不相同
// 则采用从父组件传递过来的处理函数
old.fns = cur
on[name] = old
}
}
// 删除 vm 上之前存在、现在不存在的事件
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}

vue\src\shared\util.js

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

export function isUndef (v: any): boolean %checks {
return v === undefined || v === null
} export function isDef (v: any): boolean %checks {
return v !== undefined && v !== null
}

1.1.3 渲染初始化与数据监听

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

export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) // $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data /* istanbul ignore else */
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. Net中httpResponse和httpRequest的简单实用;
  2. 栈式自动编码器(Stacked AutoEncoder)
  3. ITK 3.20.1 VS2010 Configuration 配置
  4. C#获取本机mac地址
  5. Linux-IP地址后边加个/8(16,24,32)是什么意思?
  6. 如何在不同编程语言中获取现在的Unix时间戳(Unix timestamp)?
  7. How-to Dump Keys from Memcache--reference
  8. Java 之 Spring加载(Java之负基础实战)
  9. [AHOI 2009]chess 中国象棋
  10. SQL学习指南之查询入门
  11. CF_2018-2019 Russia Open High School Programming Contest (Unrated, Online Mirror, ICPC Rules, Teams Preferred)
  12. Android 浏览器内 H5 电脑 Chrome 调试
  13. AT24C0X I2C通信原理
  14. caffe2学习
  15. PHP使用 zip 扩展压缩文件
  16. Android之封装好的异步网络请求框架
  17. 快速切题 sgu120. Archipelago 计算几何
  18. selenium安装后,需要安装浏览器驱动
  19. Tmux入门教程
  20. IDEA 实用注册码自动生成

热门文章

  1. css 小细节
  2. 苦B程序员的数据验证之路
  3. mac 中一些日常小问题与快捷键
  4. 《PHP设计模式大全》系列分享专栏
  5. servlet核心技术2
  6. java基础---类和对象(2)
  7. 家庭账本开发day06
  8. .Net Core with 微服务 - Polly 服务降级熔断
  9. 【剑指offer】03.数组中重复的数组
  10. FPGA经典:Verilog传奇与基于FPGA的数字图像处理原理及应用