Vue 源码阅读记录,MVVM 核心精读
要写 Vue.js 的源码分析,可以写一个系列。但是大体上,都知道了 Vue.js 的核心有那些东西。最重要,莫过于依赖收集了。在开始之前,这篇文章要回答如下几个问题。
PS: 内容有点长,看核心部分即可。而核心问题,看看下面的 QA。
-
Vue 是什么?
Vue 是 JavaScript 的一款 MVVC 框架,从2014年由作者 Evan You 推出以来以简单易上手著称,搭配 options object style API 和易于理解的文档。在国内外迅速收到了一大片 star,成为了 GitHub 社区的明星项目。
其组成部分如下:
- MVVM = Observer + Dep + Watcher
- VirtualDOM = (VNode = instance.render()) + Update(Patch(diff(newVNode, oldVNode)))
- Component<functional|abstract|class|HOC> = new Vue(ComponentOptions) + VirtualDOM
- ComponentOptions = name + filters + mixins + filters + components + props + data + computed + watch + provide + inject + methods + LifeCycle + template/render
- Vue.compile/vue-template-compiler - 模版编译
-
什么是 MVVM 以及 Vue 如何实现 MVVM?
Model-View-ViewModel 是一种软件架构,我的理解是 MVC/P(Model-View-Controller)的进化版。
从分层的角度去看,最上层是 View,中间层是 Controller/Presenter/ViewModel, 最下层就是 Model。
Model
声明数据模型,View
声明视图,ViewModel
声明视图与数据模型之间的绑定关系。当 Model 更新时 ViewModel 也被框架随之更新。这样做的好处是进一步解藕,将数据与视图的绑定关系抽象成 ViewModel 的 directives。通过对 Model 的赋值更新 View ,而无需手动编写 controller 更新 view。
Vue 通过数据劫持实现 MVVM 架构。其核心是 Observer、Dep 和 Watcher 三个构造函数。component options 声明中的 data 即 reactive object 就是 Model,而 template 就是 View,其中的指令
v-bind
,v-for
等就是 ViewModel 的绑定关系的实现。 -
Vue 如何实现依赖收集?
主要基于 Observer, Dep, Watcher 三个对象实现:- Observer 观察者:在 Vue2 的 option API 模式中,使用 Observer(观察者,基于 ES5 的
Object.defineProperty
API 实现)生成响应式对象(Reactive Object),拦截对象每个 key 的 getter/setter 植入dep.depend()
/dep.notify()
即 Sub/Pub。 - Dep 依赖管理: Observer 跟 Watcher 的 依赖关系 的 动态收集器。
- Watcher 订阅者: 在
Watcher.prototype.get()
中pushTarget(this)
指定 Dep.target 并调用this.getter
订阅所有的 deps 依赖关系,并在 setter() -> dep.notify() 后执行所有 deps 中 watcher 的update()
更新依赖。
当访问响应式对象属性时,会进入 getter 劫持函数的逻辑。
1. 如果存在有效的Dep.target
(即当前收集依赖的 watcher),
2. 调用watcher.addDep(dep)
收集当前属性的 dep 对象作为 watcher 的依赖,
3. 同时执行dep.addSub(this)
收集 watcher 作为 dep 的 subscribes 订阅者。
当对响应式对象的属性赋值时,会进入 setter 劫持函数的逻辑。
1. 执行dep.notify()
通知 dep.subs 中所有的订阅者 watcher,执行watcher.update()
更新之前访问响应式对象属性 getter 时收集的所有 deps 依赖,
2. 如果 watcher 不是 sync 同步执行的,则会被执行queueWatcher(this)
放进队列中在nextTick
后执行,
1. watcher 在 computed 属性中是 lazy 的,不会立即计算其值,而是在执行计算属性 computed property 的 getter 时才执行watcher.get()
方法获取 value
2. 每次在执行watcher.get()
方法前,会将当前 watcher 赋值为Dep.target
(即收集依赖的全局唯一的目标 watcher 对象),然后才执行watcher.getter()
方法计算/获取watcher.value
(以此在执行过程中收集依赖),当执行完 getter 函数获取懂啊 value 的值之后,执行watcher.cleanupDeps()
方法给 depIds, deps 与 newDepIds, newDeps 进行换位(赋值 depIds 与 deps 为 newDepIds 与 newDeps),并清空 newDepIds 与 newDeps (用于addDep
时进行是否已收集过该依赖的比对)。
3.queueWatcher
对所有的 watcher 按顺序(以 id 升序) 排序后,执行watcher.run()
方法,执行this.cb.call(this.vm, value, oldValue)
方法(即传入新值、旧值并在当前 vm 上下文执行回调) 。 - Observer 观察者:在 Vue2 的 option API 模式中,使用 Observer(观察者,基于 ES5 的
-
Vue 如何处理 data, computed, watch?
在 Vue.prototype._init 函数中执行了 initState, 在 initState 中分别执行 initProps, initMethods, initData, initComputed, initWatch。让我们先看看 initData 的实现:
- initData 时,执行
getData
函数,将返回值赋值给vm._data
属性, - 然后通过 proxy 将 data 对象中各属性的 key 的代理到了 vm 实例上访问,这样可以直接通过
this[key]
读取,但访问的实质上是vm._data[key]
。 - 最后
observe(data)
对象,添加观察的 getter/setter。然后访问实例属性时,返回它的值。
而 initComputed 则使用到了其 MVVM 的核心之
Watcher
,看看 initComputed 做了什么:- 生成私有属性
watchers = vm._computedWatchers = {}
记录所有的 computed property - for 循环 computed 中的 key 取对应的 getter/setter 生成实例化 watcher 的 options
watchers[key] = new Watcher
,为每个 computed 属性创建 watcher 实例- 通过
defineComputed
函数为每个当前的每个 key 生成createComputedGetter
到 vm 上 - 并在访问该属性时执行这个 computed getter,返回最新的经计算后的值
initWatch 的实现:
- for 了一遍 $options.watch 对象然后为每个 key 执行
createWatcher(vm, key, handler)
函数,其中 handler 的值为watch[key]
。 - 而
createWatcher
则执行了Vue.prototoype.$watch
方法new Watcher(vm, expOrFn, cb, options)
- 判断声明 watcher 的 options 是否
options.immediate
如果为true
则立即执行 cb 即 handler, 也就是watch[key]
- 返回
unwatchFn
取消当前 watcher 的 all dependencies' subscriber
- initData 时,执行
-
Watcher 被实例化的几个场景?
-
在
initComputed
函数中实例化了 watcher 对象,用于监听 computed 中属性的变化,并重新执行watcher.get()
(用户声明 computedProperty 时的 getter 函数)返回的新的计算后的值。var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions )
-
在
initWatch
和Vue.prototype.$watch
中实例化了 watcher 对象,实例化用户手动添加的watcher
。Vue.prototype.$watch = function ( expOrFn, cb, options ) { var vm = this; if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {}; options.user = true; var watcher = new Watcher(vm, expOrFn, cb, options); if (options.immediate) { try { cb.call(vm, watcher.value); } catch (error) { handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\"")); } } return function unwatchFn () { watcher.teardown(); } };
-
在
mountComponent
函数中实例化了 watcher 对象。当 render 函数依赖的 reactive property 变化时,触发watcher.update()
的同时执行 vm._update 函数走 VNode 的 Diffpatch
方法。const updateComponent = function () { vm._update(vm._render(), hydrating); }; // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true /* isRenderWatcher */);
-
-
VueRouter 在何处被触发 RouterView 的更新?
VueRouter 源码记录 - 搞懂 router hooks 和 RouterView 更新的底层逻辑
Vue 源码解析
解析版本为 Vue.js 2.6.14,为 V2+ 最新版。一行一行,精读细读。这世界上 star 前五的框架之一,究竟有何神奇魔力?让我来轻轻的揭开它神秘的面纱。
Vue 构造函数
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')
}
// 执行 _init 函数,传入 options
this._init(options) {1}
}
// 注入各种 mixin 到 Vue.prototype 原型对象上
// 这就是 {1} 中的 `Vue.prototype._init` 实现
// 这里面初始化了 lifecycle, events, render, injections, state, provide
// 调用了 beforeCreate & created hook
initMixin(Vue)
// 混入 $data, $props 和 $watch
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
让我们先从 initMixin
开始看起:
niexport function initMixin (Vue: Class<Component>) {
// 写入 _init 方法,即上面提到的 initMixin 和 构造函数中执行的 `this._init(options)`
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++ // Vuejs 内部组件的 UUID
let startTag, endTag
// 开启性能监听,使用浏览器的 performance API
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
// const perf = inBrowser && window.performance
// mark = tag => perf.mark(tag)
mark(startTag)
}
// a flag to avoid this being observed
// 不监听 Vue 实例
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 {
// 初始化实例的 $options 属性
// 它等于合并了 super.options(超类 options) + options(当前入参 options) + vm(当前实例属性)
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
// 为 vm 中的属性访问增加 has, get, set 的 handler
// 判断是否是保留字,是否为内部属性,是否存在未定义响应式属性
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 内部实现了大量跟 ComponentOptions 有关的函数
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')
/* 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)
}
}
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// 赋值当前组件的 $options 记录组件自身的 $options 选项
// $options 继承自 `vm.constructor.options` => defaultVueConstructorOptions?
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
// TODO:这一段没看明白???!!!
// 赋值 parentVnode.componentOptions 到 opts._parent 属性上?
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
// 使用传入的 options.render 函数作为 render 方法
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
*/
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component // 当前实例
): Object {
if (process.env.NODE_ENV !== 'production') {
// 检查 child 的组件名是否合法
checkComponents(child)
}
// TODO: 什么情况下 child 是个函数?
// 如果是个函数,就取 child.options 为 child
if (typeof child === 'function') {
child = child.options
}
// 这里做的都是对各种语法对兼容和适配
normalizeProps(child, vm) // 拦截校验、并重新赋值 options.props
normalizeInject(child, vm) // 拦截校验、并重新赋值 options.inject
normalizeDirectives(child) // 拦截校验、并重新赋值 options.directives
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
// 合并extends
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
// 合并mixins
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
// merge each mixin use mergeOption method
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)
}
}
// 运行特定的合并策略
// https://github.com/vuejs/vue/blob/612fb89547711cacb030a3893a0065b785802860/src/core/util/options.js
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
/**
* Default strategy.
*/
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}
/**
* Validate component names
*/
function checkComponents (options: Object) {
// 遍历当前实例 componentOptions 中 components 属性
for (const key in options.components) {
validateComponentName(key)
}
}
// 验证组件名称是否符合 HTML5 specification 中的 custom element name
// 验证是否是 isBuiltInTag 或者 config.isReservedTag
export function validateComponentName (name: string) {
if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'should conform to valid custom element name in html5 specification.'
)
}
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
)
}
}
/**
* Ensure all props option syntax are normalized into the
* Object-based format.
*/
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
// 将数组的 props: string[] 转为 对象驼峰 res[name] = {type: null}
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val)
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
// 如果是个对象声明则转一下
for (const key in props) {
val = props[key] // key's value
name = camelize(key) // camelizedKey
res[name] = isPlainObject(val)
? val
: { type: val } // 如果不是对象则转为对象 { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
// 重新赋值 options.props
options.props = res
}
/**
* Normalize all injections into Object-based format
*/
function normalizeInject (options: Object, vm: ?Component) {
const inject = options.inject
if (!inject) return
const normalized = options.inject = {}
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] }
}
} else if (isPlainObject(inject)) {
for (const key in inject) {
const val = inject[key]
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
)
}
}
/**
* Normalize raw function directives into object format.
*/
function normalizeDirectives (options: Object) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def }
}
}
}
}
// 返回构造函数 options
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
// TODO: 如果是 super,即非 Vue.extend 来的组件构造函数
// https://github.com/vuejs/vue/blob/0603ff695d2f41286239298210113cbe2b209e28/src/core/global-api/extend.js#L43
// Sub['super'] = Super
if (Ctor.super) {
// 如果来自超类,则继续递归获取超类的 options
const superOptions = resolveConstructorOptions(Ctor.super)
// https://github.com/vuejs/vue/blob/0603ff695d2f41286239298210113cbe2b209e28/src/core/global-api/extend.js#L73
// Sub.superOptions = Super.options
const cachedSuperOptions = Ctor.superOptions
// 引用不同?为何会引用不同?
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
// 拷贝 modifiedOptions 到 Ctor.extendOptions
extend(Ctor.extendOptions, modifiedOptions)
}
// 重新合并 Ctor.options,类似:
// https://github.com/vuejs/vue/blob/0603ff695d2f41286239298210113cbe2b209e28/src/core/global-api/extend.js#L39
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
// 构造函数的 options
// https://github.com/vuejs/vue/blob/0603ff695d2f41286239298210113cbe2b209e28/src/core/global-api/extend.js#L39
// Sub.options = mergeOptions(Super.options, extendOptions)
// latest 即当下 Ctor 的 options
const latest = Ctor.options
// https://github.com/vuejs/vue/blob/0603ff695d2f41286239298210113cbe2b209e28/src/core/global-api/extend.js#L75
// Sub.sealedOptions = extend({}, Sub.options)
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
我们看到这里有几个生命周期方法,一个个去看看:
initLifecycle(vm) // {1}
initEvents(vm) // {2}
initRender(vm) // {3}
callHook(vm, 'beforeCreate')
initInjections(vm) // {4} resolve injections before data/props
initState(vm) // {5} resolve inner state
initProvide(vm) // {6} resolve provide after data/props
callHook(vm, 'created')
{1} initLifecycle
方法,顾名思义,应该是初始化 lifeCycle 生命周期相关的:
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
// 存在 parent 并且非 options.abstract 抽象组件
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
// 找到非抽象组件后,将当前组件实例传给 `parent.$children`
parent.$children.push(vm)
}
// 赋值 $parent 属性,挂载 $parent 的引用
vm.$parent = parent
// 挂载 $root 根节点的引用
vm.$root = parent ? parent.$root : vm
// 赋值 $children = [] 为空数组
vm.$children = []
// 赋值 $refs 为空对象
vm.$refs = {}
// 相关私有状态的赋值
vm._watcher = null
vm._inactive = null
vm._directInactive = false
// 已装载 = false
vm._isMounted = false
// 已销毁 = false
vm._isDestroyed = false
// 正在销毁中 = false
vm._isBeingDestroyed = false
}
{2} 接下来是 initEvents(vm)
方法,顾名思义看起来是初始化事件相关函数。
export function initEvents (vm: Component) {
// 挂载 _events 声明,是 pub-sub 实现中存储 listeners 的对象
vm._events = Object.create(null)
// TODO: What `_hasHookEvent` did?
vm._hasHookEvent = false
// init parent attached events
// 初始化在 parent 中声明的 listeners
const listeners = vm.$options._parentListeners
if (listeners) {
// 更新 parentListeners 到当前实例 vm
updateComponentListeners(vm, listeners)
}
}
let target: any
// pub-sub 的 on 实现,即添加 event listener
function add (event, fn) {
target.$on(event, fn)
}
// pub-sub 的 off 实现,即移除 event listener
function remove (event, fn) {
target.$off(event, fn)
}
// only execute event listener once
function createOnceHandler (event, fn) {
// 闭包保存当前的 target 引用
const _target = target
return function onceHandler () {
// 一旦函数执行完成
const res = fn.apply(null, arguments)
// 且返回了有效(truly)的res
if (res !== null) {
// 则执行 off 卸载掉该 onceHandler 函数
_target.$off(event, onceHandler)
}
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
// 赋值 target
target = vm
// 开始更新 listeners
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
// 更新完成事件 listeners 后释放 target 引用
target = undefined
}
export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
def = cur = on[name] // current event listener
old = oldOn[name] // old event listener
event = normalizeEvent(name) // normalize event by name
/* istanbul ignore if */
// __WEEX__ 环境变量就过了,暂时忽略
if (__WEEX__ && isPlainObject(def)) {
cur = def.handler
event.params = def.params
}
// warning when `curr` is undefined
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) { // when `old` is undefined. 即不存在旧的事件定义时
if (isUndef(cur.fns)) {
// 重新声明并赋值 curr 和 on[name] 为新的 FnInvoker
cur = on[name] = createFnInvoker(cur, vm)
}
if (isTrue(event.once)) {
// 添加 onceHandler,TODO: 为什么这里有第三个参数?原函数中并未声明
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
// 将当前函数添加到 target(即当前vm) 中,TODO:后面这几个参数又在哪里用了?
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
// 如果两者引用不等
old.fns = cur // old.fns = cur
on[name] = old // 重新赋值 on[name] 引用到 old
}
}
for (name in oldOn) {
// 前面 for 了一遍 on, 现在 for 一遍 oldOn
if (isUndef(on[name])) {
// 如果 oldOn[name] 在新的 on[name] 中是未定义的
event = normalizeEvent(name)
// 则删除掉改事件
remove(event.name, oldOn[name], event.capture)
}
}
}
// 通过入参事件名称 name 初始化事件描述符
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, // &~!name ?!
once,
capture,
passive
}
})
// 工厂方法,返回绑定了 this 的 invoker function
// 感觉这个函数的核心目的:
// 1. 初始化 数据结构,返回 invoker 函数
// 2. 内部使用 invokeWithErrorHandling 执行函数
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
}
// 添加 handleError 错误追踪
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
// 如果是 promise,则添加 .catch 并处理事件
// 所以 res._handled 只是此处的 flag 用于判断是否添加 handleError 错误错误
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
// 绑定当前出错 vm 堆栈提示
export function handleError (err: Error, vm: any, info: string) {
// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
// See: https://github.com/vuejs/vuex/issues/1505
pushTarget()
try {
if (vm) {
let cur = vm
while ((cur = cur.$parent)) {
// 递归执行 子 -> 父 的 errorCaptured hook 钩子函数
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e) {
globalHandleError(e, cur, 'errorCaptured hook')
}
}
}
}
}
globalHandleError(err, vm, info)
} finally {
popTarget()
}
}
// Vue 内部的全局错误处理 handler
function globalHandleError (err, vm, info) {
if (config.errorHandler) {
try {
// 使用配置的 errorHandler
// 比如 Sentry 等第三方错误监控系统就会使用此配置
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
// if the user intentionally(有意的) throws the original error in the handler,
// do not log it twice
if (e !== err) { // 这个判断很精髓
logError(e, null, 'config.errorHandler')
}
}
}
logError(err, vm, info)
}
// 打印错误及报错组件的堆栈信息
function logError (err, vm, info) {
if (process.env.NODE_ENV !== 'production') {
// 看下方实现
warn(`Error in ${info}: "${err.toString()}"`, vm)
}
/* istanbul ignore else */
// 环境判断,浏览器或 weex 中且 console 不为 undefined 则 console.error
if ((inBrowser || inWeex) && typeof console !== 'undefined') {
console.error(err)
} else {
throw err
}
}
// warn 在非 production 环境下的实现
warn = (msg, vm) => {
// 注意此处的 generateComponentTrace
const trace = vm ? generateComponentTrace(vm) : ''
if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace)
} else if (hasConsole && (!config.silent)) {
console.error(`[Vue warn]: ${msg}${trace}`)
}
}
// 递归 vm = child -> parent 添加到 tree 中并在最后生成 log
generateComponentTrace = vm => {
if (vm._isVue && vm.$parent) {
const tree = []
// 当前递归调用的次数
let currentRecursiveSequence = 0
while (vm) {
// 一旦 tree 中已经有值,则进入下面检查
if (tree.length > 0) {
const last = tree[tree.length - 1]
// 是否是同一个构造函数构造的实例
if (last.constructor === vm.constructor) {
// 如果是的话则此处是个递归
// 此处没有 push vm 到 tree 中
currentRecursiveSequence++
vm = vm.$parent
continue
} else if (currentRecursiveSequence > 0) {
// 如果递归结束了,则 last 变成一个数组对应下方的 Array.isArray(vm) 判断
// [last, 递归调用次数] 对应下方 ${formatComponentName(vm[0])}... (${vm[1]} recursive calls)
tree[tree.length - 1] = [last, currentRecursiveSequence]
// 重置 currentRecursiveSequence
currentRecursiveSequence = 0
}
}
tree.push(vm)
vm = vm.$parent
}
return '\n\nfound in\n\n' + tree
.map((vm, i) => `${
i === 0 ? '---> ' : repeat(' ', 5 + i * 2)
}${
Array.isArray(vm)
? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)`
: formatComponentName(vm)
}`)
.join('\n')
} else {
return `\n\n(found in ${formatComponentName(vm)})`
}
}
{3} initRender(vm)
至此终于把上一个 initEvent
搞完了。开始新的一个函数,oh,render
这可是 jsx 中的核心函数了,看看 Vuejs 怎么初始化的吧。我有预感,这个比上面的复杂得多得多。
然而实际上并不复杂,initRender
export function initRender (vm: Component) {
// _vnode 即 vnode 节点,初始化为空
vm._vnode = null // the root of the child tree
// TODO: What's `v-once` mean?
vm._staticTrees = null // v-once cached trees
const options = vm.$options
// TODO: parentNode 是父节点?
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
// parentNode.context 是父节点的渲染上下文,但具体是什么?
const renderContext = parentVnode && parentVnode.context
// _renderChildren?? 我的理解是所有的 children
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') {
// 开发环境下,监听 $attrs 属性。定义 $attrs 是 parentVnode.data,且 readonly
// defineReactive$$1(obj,key,val,customSetter,shallow)
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
// isUpdatingChildComponent 是一个 flag,用于判断当前是否正在更新组件
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
// 开发环境下,监听 $listeners 属性。定义 $listeners 是 options._parentListeners,且 readonly
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
// 生产环节,不拦截 setter 不抛出 warning
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
resolveSlots(vm)
,将 children 转换为 $slots
对象。
/**
* Runtime helper for resolving raw children VNodes into a slot object.
*/
export function resolveSlots (
children: ?Array<VNode>,
context: ?Component
): { [key: string]: Array<VNode> } {
if (!children || !children.length) {
return {}
}
const slots = {}
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i]
const data = child.data
// remove slot attribute if the node is resolved as a Vue slot node
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot
}
// named slots should only be respected if the vnode was rendered in the
// same context.
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
const name = data.slot
const slot = (slots[name] || (slots[name] = []))
if (child.tag === 'template') {
slot.push.apply(slot, child.children || [])
} else {
slot.push(child)
}
} else {
(slots.default || (slots.default = [])).push(child)
}
}
// ignore slots that contains only whitespace
for (const name in slots) {
if (slots[name].every(isWhitespace)) {
delete slots[name]
}
}
return slots
}
function isWhitespace (node: VNode): boolean {
return (node.isComment && !node.asyncFactory) || node.text === ' '
}
{4} 嗯,继续看看 initInjections
函数的实现:
/**
* In some cases we may want to disable observation inside a component's
* update computation.
*/
var shouldObserve = true
function toggleObserving(value) {
shouldObserve = value
}
function initInjections(vm) {
var result = resolveInject(vm.$options.inject, vm)
if (result) {
// 无需观察,即无需收集依赖
toggleObserving(false)
Object.keys(result).forEach(function(key) {
/* istanbul ignore else */
{
defineReactive$$1(vm, key, result[key], function() {
warn(
'Avoid mutating an injected value directly since the changes will be ' +
'overwritten whenever the provided component re-renders. ' +
'injection being mutated: "' +
key +
'"',
vm
)
})
}
})
toggleObserving(true)
}
}
// 解析并返回 inject,如果 componentOptions 声明了 inject 对象
// 在解析之前,当然要看看 normalize 喽,根据 Vuejs 的代码习惯,所有的这些东西
// 都是先 normalized(这一步是为了兼容各种五花八门的写法)
// 然后 resolved(这一步是为了才是正儿八经的从中取值,同时加了很多包装)
function resolveInject(inject, vm) {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
var result = Object.create(null)
var keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject)
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') {
continue
}
var provideKey = inject[key].from
var source = vm
// 此处的 while 则是从 source 开始往上递归回溯
// 直到找到了 _provided[provideKey] 的值,如果找到了当然就 break 了
while (source) {
// 我们看到了 `_provided` 关键字
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
// 如果没有或者没找到 source,则取 default 为值了撒
if (!source) {
if ('default' in inject[key]) {
var provideDefault = inject[key].default
result[key] =
typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else {
// 如果没有声明 default,则 warn
warn('Injection "' + key + '" not found', vm)
}
}
}
// 返回解析后的结果
return result
}
}
/**
* Normalize all injections into Object-based format
*/
function normalizeInject(options, vm) {
var inject = options.inject
if (!inject) {
return
}
var normalized = (options.inject = {})
if (Array.isArray(inject)) {
for (var i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] }
}
} else if (isPlainObject(inject)) {
for (var key in inject) {
var val = inject[key]
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val }
}
} else {
warn(
'Invalid value for option "inject": expected an Array or an Object, ' +
'but got ' +
toRawType(inject) +
'.',
vm
)
}
}
{6} 既然有 injection 那么一定有 provide
了撒:
/* */
// 好奇,这玩意有没有 normalize function 呢,找了下,没有找到
function initProvide(vm) {
var provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function' ? provide.call(vm) : provide
}
}
还有 {5} 就是initState(vm)
:
ep function initState(vm) {
// _watchers = [] 这一步在后面的使用中有深意
vm._watchers = []
var opts = vm.$options // Component Options
if (opts.props) {
initProps(vm, opts.props) // 初始化、校验、解析 props
}
if (opts.methods) {
initMethods(vm, opts.methods) // 初始化、校验、解析 methods
}
if (opts.data) {
initData(vm) // 初始化 data
} else {
// 如果没有 data 选项,则设置个 default data
observe((vm._data = {}), true /* asRootData */)
}
if (opts.computed) {
initComputed(vm, opts.computed) // 初始化 computed 属性,收集依赖
}
// Firefox has a "watch" function on Object.prototype...
// var nativeWatch = {}.watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch) // 初始化 watch 属性
}
}
// 初始化 props,看里面主要做的事是遍历了一道 propsData
function initProps(vm, propsOptions) {
var propsData = vm.$options.propsData || {}
var props = (vm._props = {})
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
var keys = (vm.$options._propKeys = [])
var isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
var loop = function(key) {
keys.push(key)
// 校验 prop 同时取出 value
var value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
{
var hyphenatedKey = hyphenate(key)
if (
isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)
) {
warn(
'"' +
hyphenatedKey +
'" is a reserved attribute and cannot be used as component prop.',
vm
)
}
// 定义成 reactive 属性
// defineReactive$$1(obj, key, val, customSetter, shallow)
defineReactive$$1(props, key, value, function() {
if (!isRoot && !isUpdatingChildComponent) {
warn(
'Avoid mutating a prop directly since the value will be ' +
'overwritten whenever the parent component re-renders. ' +
"Instead, use a data or computed property based on the prop's " +
'value. Prop being mutated: "' +
key +
'"',
vm
)
}
})
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, '_props', key)
}
}
for (var key in propsOptions) loop(key)
toggleObserving(true)
}
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function initMethods(vm, methods) {
var props = vm.$options.props
for (var key in methods) {
{
if (typeof methods[key] !== 'function') {
warn(
'Method "' +
key +
'" has type "' +
typeof methods[key] +
'" in the component definition. ' +
'Did you reference the function correctly?',
vm
)
}
if (props && hasOwn(props, key)) {
warn('Method "' + key + '" has already been defined as a prop.', vm)
}
if (key in vm && isReserved(key)) {
warn(
'Method "' +
key +
'" conflicts with an existing Vue instance method. ' +
'Avoid defining component methods that start with _ or $.'
)
}
}
// 将 ComponentOptions 中所有的 methods 挂载到 vm 实例上
vm[key] =
typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
/**
* Check if a string starts with $ or _
*/
function isReserved(str) {
var c = (str + '').charCodeAt(0)
return c === 0x24 || c === 0x5f
}
// 初始化 Data,这一步跟 Watcher 应该有关系才是
function initData(vm) {
var data = vm.$options.data
// 赋值到 vm._data,执行 getData 方法(清空了 Dep.target 后调用 data 函数)
// 然后使用 proxy 将属性代理到 vm 上但访问但实质上是 vm._data[key]
// 最后 observe(data) 对象,添加观察
data = vm._data =
typeof data === 'function' ? getData(data, vm) : data || {}
if (!isPlainObject(data)) {
data = {}
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
var keys = Object.keys(data)
var props = vm.$options.props
var methods = vm.$options.methods
var i = keys.length
while (i--) {
var key = keys[i]
{
if (methods && hasOwn(methods, key)) {
warn(
'Method "' + key + '" has already been defined as a data property.',
vm
)
}
}
if (props && hasOwn(props, key)) {
warn(
'The data property "' +
key +
'" is already declared as a prop. ' +
'Use prop default value instead.',
vm
)
} else if (!isReserved(key)) {
proxy(vm, '_data', key)
}
}
// observe data, make data be reactively
observe(data, true /* asRootData */)
}
// 执行 data 函数获取其返回数据
function getData(data, vm) {
// 执行前清空了 Dep.target,也就是说这一步不需要收集依赖
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, 'data()')
return {}
} finally {
popTarget()
}
}
// 初始化 computed 属性
function initComputed(vm, computed) {
// $flow-disable-line
// 先创建和挂载私有属性 _computedWatchers
var watchers = (vm._computedWatchers = Object.create(null))
// computed properties are just getters during SSR
var isSSR = isServerRendering()
// for 循环 computed 中的 key 取/生成对应的 getter/setter
for (var key in computed) {
var userDef = computed[key]
var getter = typeof userDef === 'function' ? userDef : userDef.get
if (getter == null) {
warn('Getter is missing for computed property "' + key + '".', vm)
}
if (!isSSR) {
// create internal watcher for the computed property.
// Watcher(vm, expOrFn, cb, options, isRenderWatcher)
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions // { lazy: true }
// this.value = this.lazy ? undefined : this.get()
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else {
if (key in vm.$data) {
warn(
'The computed property "' + key + '" is already defined in data.',
vm
)
} else if (vm.$options.props && key in vm.$options.props) {
warn(
'The computed property "' + key + '" is already defined as a prop.',
vm
)
}
}
}
}
function defineComputed(target, key, userDef) {
var shouldCache = !isServerRendering() // true
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function() {
warn(
'Computed property "' +
key +
'" was assigned to but it has no setter.',
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter(key) {
return function computedGetter() {
var watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// in Watcher defintion: this.dirty = this.lazy // for lazy watchers
// and the compouted properties are always `lazy` & `dirty`
if (watcher.dirty) {
watcher.evaluate()
}
// 为当前 computedGetter 收集 dep
if (Dep.target) {
// Depend on all deps collected by this watcher.
// (for dep of watcher.deps) { dep.depend() }
watcher.depend()
}
return watcher.value
}
}
}
function createGetterInvoker(fn) {
return function computedGetter() {
return fn.call(this, this)
}
}
// 初始化 watch 属性
function initWatch(vm, watch) {
for (var key in watch) {
var handler = watch[key]
if (Array.isArray(handler)) {
for (var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher(vm, expOrFn, handler, options) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
// 所以 vue componentOptions.watcher 的 handler 可以声明成 string
// 然后从 vm 上面取. okay, i got it.
if (typeof handler === 'string') {
handler = vm[handler]
}
// 用的 vm.$watch 原型对象方法,让我们继续追踪
return vm.$watch(expOrFn, handler, options)
}
这儿是 stateMixin
方法,内部实现了 Vue.prototype.$watch
:
function stateMixin(Vue) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
var dataDef = {}
// 返回实例私有属性 _data
dataDef.get = function() {
return this._data
}
// 返回实例私有属性 _props
var propsDef = {}
propsDef.get = function() {
return this._props
}
// 阻止对 data 和 props 的赋值重写
{
dataDef.set = function() {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function() {
warn('$props is readonly.', this)
}
}
// 使用 $data 表明对 _data 私有属性的包装,增强了 getter/setter
Object.defineProperty(Vue.prototype, '$data', dataDef)
// 使用 $props 表明对 _props 私有属性的包装,增强了 getter/setter
Object.defineProperty(Vue.prototype, '$props', propsDef)
// 响应式的 $set & $delete
Vue.prototype.$set = set // $set(target: Array|Object, key: string, val: any )
Vue.prototype.$delete = del // $del(target: Array|Object, key: string)
// 响应式的 $watch 🥱
Vue.prototype.$watch = function(expOrFn, cb, options) {
var vm = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true // it means this watcher was from user declare manual
// 此处的 watcher 会立即执行,因为非 lazy
// 会立即执行 this.value = this.get() 并开始依赖收集
var watcher = new Watcher(vm, expOrFn, cb, options)
// 如果传入的 watch options.immediate 则立即执行回调
if (options.immediate) {
try {
// 立即执行 cb
cb.call(vm, watcher.value)
} catch (error) {
handleError(
error,
vm,
'callback for immediate watcher "' + watcher.expression + '"'
)
}
}
return function unwatchFn() {
watcher.teardown()
}
}
}
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
function set(target, key, val) {
if (isUndef(target) || isPrimitive(target)) {
warn(
'Cannot set reactive property on undefined, null, or primitive value: ' +
target
)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
// 执行数组的包装方法,splice
target.splice(key, 1, val)
return val
}
// 如果 key 已存在 target 中
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
var ob = target.__ob__
if (target._isVue || (ob && ob.vmCount)) {
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive$$1(ob.value, key, val)
ob.dep.notify()
return val
}
/**
* Delete a property and trigger change if necessary.
*/
function del(target, key) {
if (isUndef(target) || isPrimitive(target)) {
warn(
'Cannot delete reactive property on undefined, null, or primitive value: ' +
target
)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
var ob = target.__ob__
if (target._isVue || (ob && ob.vmCount)) {
warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
if (!hasOwn(target, key)) {
return
}
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}
这是 vue.js 内部的 event modifer 的状态捕获实现
https://vuejs.org/v2/guide/events.html#Event-Modifiers
精炼源码
上面的走远了,逐行读 Vue.js 代码并未带给我任何收益,反而浪费了许多时间,甚至带来了负面情绪拖延。
还是从核心方法上来看起走吧。
我觉得 Vue.js 之所以四不像的原因就是它什么都有。它就是 [MVVM, Component, VirtualDOM, Filters, Template, Directives, Provide/Inject ] 的集合,而且各种吸收,有点营养过剩。
而 React 就比较简单,就是 UI = React(state)
。而 Vue 就是 MVVM JS SDK.
MVVM
Dep + Observer + Watcher/Subscriber 实现了 Vuejs 的 MVVM。
先看 Dep 的源码:
/* */
var uid = 0;
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
/**
* @param {Watcher} sub
*/
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
// Watcher 收集依赖 Deps => wathcher.deps
Dep.prototype.depend = function depend () {
if (Dep.target) {
// 给当前 Dep.target 的 Watcher 添加 dep 实例
Dep.target.addDep(this);
}
};
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null; // Dep.target 是个 Watcher
var targetStack = [];
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
Observer 的源码:
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep(); // __ob__.dep
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
/**
* Observe a list of Array items.
*/
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
// helpers
/**
* Augment a target Object or Array by intercepting
* the prototype chain using __proto__
*/
function protoAugment (target, src) {
/* eslint-disable no-proto */
target.__proto__ = src;
/* eslint-enable no-proto */
}
/**
* Augment a target Object or Array by defining
* hidden properties.
*/
/* istanbul ignore next */
function copyAugment (target, src, keys) {
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]);
}
}
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
/**
* Define a reactive property on an Object.
*/
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
// defineReactive$$1(obj,key) 的情况下编程式的为 val 赋值
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 此处的关键是,生命了 getter 函数,但只有访问这个属性时
// 且 Dep.target 存在时才会开始依赖收集
var value = getter ? getter.call(obj) : val;
// 执行 getter 时如果当前存在 Dep.target Watcher 说明
// 需要执行所有对该 Watcher 的 deps 以更新所有 subscribe
if (Dep.target) {
dep.depend(); // 收集依赖 => Dep.target.addDep(this) TODO:这一步需要结合下面的 Watcher 看内部具体做了哪些事情
if (childOb) {
// Deep Watch, 功能同上 TODO: 同上所述
childOb.dep.depend();
if (Array.isArray(value)) {
// Collect dependencies
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 从 getter 取值,因为 getter 可能依赖了某些 reactive object property
// TODO: 如果 getter 返回了最新的值的话,不就有问题了吗?
// 说明这时候 getter 的返回值并不会发生变化
// 如果没有 getter,就从闭包中取上次的 val
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
// diff 对比
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
// TODO:自定义 setter,执行这个的意义还不太明白。
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
// TODO: 有 getter 没有 setter 就返回?
if (getter && !setter) { return }
// 有 setter 的话直接执行 setter,这适合 computed property: { get, set } 情形
if (setter) {
setter.call(obj, newVal);
} else {
// 更新闭包中 val 的值,用于下次比对
val = newVal;
}
// 重新赋值 child Observer 对象在闭包中的值
childOb = !shallow && observe(newVal);
// Setter 赋值之后按顺序触发所有 Watchers 更新 update()
dep.notify();
}
});
}
/**
* Collect dependencies on array elements when the array is touched, since
* we cannot intercept array element access like property getters.
*/
function dependArray (value) {
for (var e = (void 0), i = 0, l = value.length; i < l; i++) {
e = value[i];
e && e.__ob__ && e.__ob__.dep.depend();
if (Array.isArray(e)) {
dependArray(e);
}
}
}
上面的代码给我影响深刻的是 Observer 所有的 getter 都是 Dep.depend() ⇒ Watcher.addDep(this) 的实现,而 setter 则是 Dep.notify() 通知所有更新,并同时执行所有 Watcher 的后续逻辑。
而 Vue.prototype.set(obj, lkey, val) 则跟 defineReactive 是包装的实现,只是做了写判断,del
方法同理。
来看看 Watcher:
/* */
var uid$2 = 0
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm
// 在 mount component 实例化时 isRenderWatcher 为 true
// 这时候挂载 _watcher 到 mounted component 实例上
if (isRenderWatcher) {
vm._watcher = this
}
// 在 initState 时候初始化了 `vm._watchers = []`
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep // watch deep
this.user = !!options.user // from user defintion like $watch or ComponentOptions.watch
this.lazy = !!options.lazy // lazy evaluate getter to get lazy value
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid$2 // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new _Set()
this.newDepIds = new _Set()
this.expression = expOrFn.toString()
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
warn(
'Failed watching path: "' +
expOrFn +
'" ' +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
// 如果是 lazy 的话,先不执行 get() 获取结果,
// 在 ComponentOptions.computed 的声明中
// 它的 get() 总是 lazy 得到的
this.value = this.lazy ? undefined : this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
Watcher.prototype.get = function get() {
// 执行 get 时候, Dep.target 就是当前 Watcher
// 此时所有的 getter/setter 都会该 Watcher 被订阅
// dep.depend() => this.addDep(dep)
pushTarget(this) // {1}
var value
var vm = this.vm
try {
// IMPORTANT: 执行 getter,传入当前上下文和实例 vm,
// 此时,如果 getter 中访问了任何 reactive object 的属性
// 则会通过上面的 {1} 赋值 Dep.target 注册依赖
// 将当前 watcher 作为 subscribe 放进每个属性 dep 中
// 而 watcher 同时也维护了对各个依赖属性的 deps 数组
// 所以是 Observer 在 observe 时创建了 Dep
// 而 watcher 在计算/获取值时赋值了 Dep.target
// 因此此时凡是被访问到的对象的 getter 都会被 watcher 收集作为依赖
// 而 dep 同时也会把 watcher 添加到 subs 中作为 subscribes
// 当下次有更新时候,watcher 通知所有的 deps notify subs 更新即可
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, 'getter for watcher "' + this.expression + '"')
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
Watcher.prototype.addDep = function addDep(dep) {
var id = dep.id
// 在 watcher 实例化之后如果 newDepIds 和 oldDepId 中都没有该 dep
// 则将当前 Watcher 实例添加在 Dep 的订阅中,dep 是所有的属性的 Observe
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// dep.subs
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
Watcher.prototype.cleanupDeps = function cleanupDeps() {
var i = this.deps.length
while (i--) {
var dep = this.deps[i]
// TODO:newDepIds 是什么?为什么要有 newDepIds?
// 从逻辑上看是从 dep 中删除 newDepIds 的 Sub
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
// 这里做的是将 newDeps/newDepIds 赋值为 deps/depIds
// 将旧的 deps/depIds 清空
var tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
Watcher.prototype.update = function update() {
/* istanbul ignore else */
// TODO: 如果是 lazy 则更新 dirty 为 true,所以 dirty 后续会作何处理?
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// TODO:如果 sync 则直接运行,那 sync 又意味着什么?
this.run()
} else {
// 否则的话,丢进 queue 然后 flushCallbacks ?
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
Watcher.prototype.run = function run() {
if (this.active) {
// 重新执行 get 方法时,如果有 reactive 属性被访问
// 会被添加到当前 watcher.addDep(dep) 中记录(该 watcher 所依赖的 reactive 的 dep 实例)
// 而经过 newDepIds 和 depIds 的比对后,
// watcher 实例被添加到 dep.subs 中用于后续执行 setter 触发更新
// 一旦进入 setter 则会 dep.notify 通知所有的 watcher.update()
var value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(
e,
this.vm,
'callback for watcher "' + this.expression + '"'
)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
Watcher.prototype.evaluate = function evaluate() {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
Watcher.prototype.depend = function depend() {
var i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
Watcher.prototype.teardown = function teardown() {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
var i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
/**
* Parse simple path.
*/
var bailRE = new RegExp('[^' + unicodeRegExp.source + '.$_\\d]')
function parsePath(path) {
if (bailRE.test(path)) {
return
}
var segments = path.split('.')
return function(obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) {
return
}
obj = obj[segments[i]]
}
return obj
}
}
/* */
var seenObjects = new _Set()
/**
* Recursively traverse an object to evoke all converted
* getters, so that every nested property inside the object
* is collected as a "deep" dependency.
* 递归遍历对象以调用所有转换的getter,这样对象中的每个嵌套属性都作为“深层”依赖项收集。
*/
function traverse(val) {
// TODO:我不明白这里这个 _traverse 的实际意义是什么?
// 而且哪儿在收集?我并没有看到收集?
_traverse(val, seenObjects)
seenObjects.clear()
}
function _traverse(val, seen) {
var i, keys
var isA = Array.isArray(val)
if (
(!isA && !isObject(val)) ||
Object.isFrozen(val) ||
val instanceof VNode
) {
return
}
if (val.__ob__) {
var depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
while (i--) {
_traverse(val[i], seen)
}
} else {
keys = Object.keys(val)
i = keys.length
while (i--) {
_traverse(val[keys[i]], seen)
}
}
}
在处理 AsyncComponent 时候的代码:
function resolveAsyncComponent(factory, baseCtor) {
// 如果有错则直接返回 errorComp
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
// 如果已经 resolved 则直接返回 resolved
if (isDef(factory.resolved)) {
return factory.resolved
}
// 当前 owner 即正在渲染的 vm 实例
// 我的理解是这一步在为调用了 AsyncComponent 收集依赖关系
var owner = currentRenderingInstance
if (
owner &&
isDef(factory.owners) &&
factory.owners.indexOf(owner) === -1
) {
// already pending
factory.owners.push(owner)
}
// 如果 loading 且 loadingComp 已经初始化好了则渲染 loading 组件
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
// 如果存在 owner 且 factory.owners 之前还未初始化过则进入初始化逻辑
if (owner && !isDef(factory.owners)) {
var owners = (factory.owners = [owner])
var sync = true
var timerLoading = null
var timerTimeout = null
owner.$on('hook:destroyed', function() {
return remove(owners, owner)
})
// 强制更新并渲染
var forceRender = function(renderCompleted) {
for (var i = 0, l = owners.length; i < l; i++) {
owners[i].$forceUpdate()
}
// 渲染完成后清空内部状态
if (renderCompleted) {
owners.length = 0
if (timerLoading !== null) {
clearTimeout(timerLoading)
timerLoading = null
}
if (timerTimeout !== null) {
clearTimeout(timerTimeout)
timerTimeout = null
}
}
}
// resolve 函数
var resolve = once(function(res) {
// cache resolved
factory.resolved = ensureCtor(res, baseCtor)
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
if (!sync) {
forceRender(true)
} else {
owners.length = 0
}
})
var reject = once(function(reason) {
warn(
'Failed to resolve async component: ' +
String(factory) +
(reason ? '\nReason: ' + reason : '')
)
// 出现错误后渲染错误组件
if (isDef(factory.errorComp)) {
factory.error = true
forceRender(true)
}
})
// 执行 factory 并传入 resolve, reject
var res = factory(resolve, reject)
// 如果返回值是个对象
if (isObject(res)) {
// 如果返回值是个对象且是个 promise
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
} else if (isPromise(res.component)) {
res.component.then(resolve, reject)
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor)
}
if (isDef(res.loading)) {
factory.loadingComp = ensureCtor(res.loading, baseCtor)
if (res.delay === 0) {
factory.loading = true
} else {
timerLoading = setTimeout(function() {
timerLoading = null
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender(false)
}
}, res.delay || 200)
}
}
if (isDef(res.timeout)) {
timerTimeout = setTimeout(function() {
timerTimeout = null
if (isUndef(factory.resolved)) {
reject('timeout (' + res.timeout + 'ms)')
}
}, res.timeout)
}
}
}
sync = false
// return in case resolved synchronously
return factory.loading ? factory.loadingComp : factory.resolved
}
}
在处理 EventEmitter 时候的代码,其实就是 Pub-Sub 的实现:
// 挂载事件属性
function initEvents(vm) {
// subs pool
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
// 父组件所有的 on listener
var listeners = vm.$options._parentListeners
if (listeners) {
// 见上面的描述,上面的源码解读有解释过函数内部的代码
updateComponentListeners(vm, listeners)
}
}
// 给 Vue.prototype 混入 EventEmitter 方法
function eventsMixin(Vue) {
var hookRE = /^hook:/
Vue.prototype.$on = function(event, fn) {
var vm = this
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
;(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
Vue.prototype.$once = function(event, fn) {
var vm = this
function on() {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$off = function(event, fn) {
var vm = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
if (Array.isArray(event)) {
for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
vm.$off(event[i$1], fn)
}
return vm
}
// specific event
var cbs = vm._events[event]
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler
var cb
var i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
Vue.prototype.$emit = function(event) {
var vm = this
{
var lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
'Event "' +
lowerCaseEvent +
'" is emitted in component ' +
formatComponentName(vm) +
' but the handler is registered for "' +
event +
'". ' +
'Note that HTML attributes are case-insensitive and you cannot use ' +
'v-on to listen to camelCase events when using in-DOM templates. ' +
'You should probably use "' +
hyphenate(event) +
'" instead of "' +
event +
'".'
)
}
}
var cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
var args = toArray(arguments, 1)
var info = 'event handler for "' + event + '"'
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
}
再看看 LifeCycle 的实现:
// 挂载 LifeCycle
function initLifecycle(vm) {
var options = vm.$options
// locate first non-abstract parent
var parent = options.parent
if (parent && !options.abstract) {
// 找到非抽象组件的 parent vm
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
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
}
var activeInstance = null
// 这个函数是用闭包保存起 prevActiveInstance
// 然后重置 activeInstance 为当前 vm,
// 最后返回重置 activeInstance 回 prevActiveInstance 的函数
function setActiveInstance(vm) {
var prevActiveInstance = activeInstance
activeInstance = vm
return function() {
activeInstance = prevActiveInstance
}
}
// 给 Vue.prototype 混入 LifeCycle 方法
function lifecycleMixin(Vue) {
// TODO: 什么时候调用了 this._update? 应该是在 init/patch 组件时
// 下方给除了 _update 函数的核心母的,就是 init/update 时
// 都执行了 vm.__patch__ 函数,并调用重置函数 restoreActiveInstance
Vue.prototype._update = function(vnode, hydrating) {
var vm = this
var prevEl = vm.$el
var prevVnode = vm._vnode
var restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
// 清除 prevEl.__vue__ 引用,释放 __vue__ 内存
// 那么 prevEl 内,无需释放了,因为上面 __patch__ 时重新赋值了
// 则在函数结束后会被清空
prevEl.__vue__ = null
}
if (vm.$el) {
// 重新给当前 $el.__vue__ 写入 vm 实例
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
// TODO: how to judge parent is an HOC? `vm.$vnode === vm.$parent._vnode`???
// 当前 $vnode,在之前的代码中是 vm.$vnode = parentVnode
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
Vue.prototype.$forceUpdate = function() {
var vm = this
if (vm._watcher) {
// 原来 $forceUpdate 就是将所有的 _watcher 给 update 一遍
// 然后这些 update 内部一定有跟 view 绑定的 getter/setter 以更新对应视图
vm._watcher.update()
}
}
// 把 callHook 复制进来看
function callHook(vm, hook) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
var handlers = vm.$options[hook]
var info = hook + ' hook'
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
Vue.prototype.$destroy = function() {
var vm = this
// 也就是说 $destroy 可能会被多次调用
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// remove self from parent
var parent = vm.$parent
// 从 parent 中移除当前 vm,释放内存
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown()
}
var i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
function mountComponent(vm, el, hydrating) {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
{
/* istanbul ignore if */
if (
(vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el ||
el
) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
callHook(vm, 'beforeMount')
var updateComponent
/* istanbul ignore if */
if (config.performance && mark) {
updateComponent = function() {
var name = vm._name
var id = vm._uid
var startTag = 'vue-perf-start:' + id
var endTag = 'vue-perf-end:' + id
mark(startTag)
var vnode = vm._render()
mark(endTag)
measure('vue ' + name + ' render', startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure('vue ' + name + ' patch', startTag, endTag)
}
} else {
updateComponent = function() {
// mounted component to generates component vnode instance
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(
vm,
updateComponent,
noop,
{
before: function before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
},
true /* isRenderWatcher */
)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
接下来的核心就是看看 Vue 中的 Component 的实现了,但据以往关于 Virtual-DOM 的经验来看,就是渲染函数 render + 虚拟节点 VNode + Diff 算法 path。并围绕这三个部分编写生命周期函数,complie & optimize,都是写老生常谈的话题了,略过吧。
🔚