创建项目
首先我们创建一个最基础的Vue 2的脚手架应用,创建方法点击这里查看,然后我们关注到项目入口文件main.js
, 引入Vue
构造函数以及App.vue
文件。完成应用的构建,我们来看看这个过程中发生了什么。
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
console.log(App)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
Vue构造函数
Vue的构造函数调用this
关键字下的__init
方法并传入对应的
// node_modules/vue/src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
// 这一行主要利用的`this`关键字的实例对象的`__proto__`会先绑定到Vue的prototype上才可以依赖这个作为检查机制
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
我们分别来看对应的Mixin
系列函数吧
initMixin
查看下方代码,我们可以总结出来在initMixin
中主要做了vm
上的内部数据跟显示数据的初始化工作,并且执行了created
以及beforeCreate
生命周期
// node_modules/vue/src/core/instance/init.js
/* @flow */
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
// VM唯一标识
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
// 自增标识符
vm._uid = uid++
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(
// 取出插件或者继承的一些配置项用于合并,在我的初始化项目里面,这个步骤可以取出vue-router的一些对应配置
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
// 用于对vm实例对象的操作进行提示
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 初始化部分基础参数(节点关系)以及初始化对应的标识符比如(_isMounted)
initLifecycle(vm)
// 初始化事件参数(_hasHookEvent, _events)
initEvents(vm)
// 初始化渲染相关参数(_vnode, _staticTrees,$vnode, $slots,$scopedSlots, _c, $createElement ,$attrs, $listeners)
// 同时这里的$attrs, $listeners实际上已经初始化了对应的数据劫持
initRender(vm)
// 调用`beforeCreate`详细方法实现我们可以看到下方,作者这里复制出来了
callHook(vm, 'beforeCreate')
// 初始化inject配置,向$parent链路上查询的vm._provided是否存在,如果存在取出附值
initInjections(vm) // resolve injections before data/props
// 初始化状态数据(vm._watchers, props, methods, data, computed, watch),那么我相信大家肯定很关注这个地方 我们下面会详细看这里。
initState(vm)
// 初始化provide到vm._provided上
initProvide(vm) // resolve provide after data/props
// 调用created的生命周期
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) {
// 如果配置el那么进行节点挂在
vm.$mount(vm.$options.el)
}
}
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// 用于做继承
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
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
// 解析构造函数的配置项,包括Options的跟super继承的
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
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) {
extend(Ctor.extendOptions, modifiedOptions)
}
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
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
// callHook 来源于 node_modules/vue/src/core/instance/lifecycle.js 调用对应的生命周期的钩子
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
// 推入空target
pushTarget()
// 配置中取出对应生命周期数组
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
// 弹出空target
popTarget()
}
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)
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
}
initState 以及 stateMixin
大家下方直接找到initState
跟开始学习
此文件正好涉及到stateMixin我们就直接一起看了吧
// node_modules/vue/src/core/instance/state.js
/* @flow */
import config from '../config'
import Watcher from '../observer/watcher'
import Dep, { pushTarget, popTarget } from '../observer/dep'
import { isUpdatingChildComponent } from './lifecycle'
import {
set,
del,
observe,
defineReactive,
toggleObserving
} from '../observer/index'
import {
warn,
bind,
noop,
hasOwn,
hyphenate,
isReserved,
handleError,
nativeWatch,
validateProp,
isPlainObject,
isServerRendering,
isReservedAttribute,
invokeWithErrorHandling
} from '../util/index'
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 文件入口函数
export function initState (vm: Component) {
// 初始化vm._watchers
vm._watchers = []
const opts = vm.$options
// 下面都是各项配置的基础检查以及执行,我们就直接关注到对应函数即可
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
// 初始化props
function initProps (vm: Component, propsOptions: Object) {
// 取出props
const propsData = vm.$options.propsData || {}
// 初始化vm._props
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
// 如果不是跟节点,那么全局标识`shouldObserve`设置为false
if (!isRoot) {
toggleObserving(false)
}
// 便利数据对象
for (const key in propsOptions) {
// 插入数组,由于是引用数据类型,所以 keys 跟 vm.$options._propKeys 会同时增加对应数据
keys.push(key)
/*
* validateProp执行内容如下
* 1. 对props是否属于boolean的类型进行检查并对其进行porps以及propsData中的配置的初始化(这里应该是因为Boolean类型的初始化逻辑独立于其他类型逻辑所以单独提出来做优先检查)
* 2. 获取对应的初始化数据,并且对于数据建立观察者(observe)[建立观察者的前提是数据是一个对象,这里的对象指的是JS中的普遍对象类型]
*/ 3. 最后放回对应的数据类型
const value = validateProp(key, propsOptions, propsData, vm)
// 定义可响应对象, 具体定义过程我们后面来学习一下`defineReactive`
defineReactive(props, key, value)
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
// 这里为props的数据进行代理,让你可以通过`this`关键字进行直接访问
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
// 切换回可创建观察者对象的全局状态
toggleObserving(true)
}
function initData (vm: Component) {
// 取出对应的data属性
let data = vm.$options.data
// 获取data的对应对象
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 要求必须是一个plainObj 也就是 Object.prototype.tostring(data) === '[object Object]'
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
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
)
}
}
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
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
// 将Object设置为Observe
observe(data, true /* asRootData */)
}
// 绑定上下文并且进行执行,同时滞空现有的target以免数据的初始化操作产生不当相应
export function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
// computedWatcher的默认配置项
const computedWatcherOptions = { lazy: true }
// 初始化计算属性
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
// 为computed创建单独的vm._computedWatchers = {}
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
// 遍历computed配置项
for (const key in computed) {
const userDef = computed[key]
// 取出对应的`get`函数
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// create internal watcher for the computed property.
// 为每个computed 配置项创建 Watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// 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)) {
// 设置对应的计算属性代理让你可以通过`this`关键字访问相关属性
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
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)
} else if (vm.$options.methods && key in vm.$options.methods) {
warn(`The computed property "${key}" is already defined as a method.`, vm)
}
}
}
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 是否缓存
const shouldCache = !isServerRendering()
// 是否仅为`get`钩子
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 (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 创建对应的计算属性的get(this代理上的)
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 如果数据脏了,那就更新一下对应的数据
if (watcher.dirty) {
watcher.evaluate()
}
// 如果评估完还有watch在进行评估, 那就调用当前watch的deps的depend
if (Dep.target) {
watcher.depend()
}
// 返回数据
return watcher.value
}
}
}
// 直接绑定执行环境进行执行
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
// 初始化方法
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
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 $.`
)
}
}
// 绑定上下文进行执行
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
function initWatch (vm: Component, watch: Object) {
// 遍历watch配置,创建watcher
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
// 通过$watch对watch进行创建
return vm.$watch(expOrFn, handler, options)
}
// stateMixin 是在 initMixin后执行的函数
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.
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._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)
}
}
// 创建对应的可读性对象$data 跟 $props
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
// 设置$set 根 $delete
Vue.prototype.$set = set
Vue.prototype.$delete = del
// 创建对应的watc
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
// 如果是用户定义的Watch,而不是系统内部的Watch有此标识
options.user = true
// 这里我们看到我们的watch 跟 computer实际上都是基于`Watcher`对象。
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
}
那么我们在学习完会成当前文件后,注意以下:
- props 中的数据如果是object的话会被初始化成
Observe
实例对象,剩下的属性会被进行defineReactive
- data 的 object 也会被初始化成
Observe
实例对象 - compute跟watch会被初始化成
Watch
实例对象,不过初始化配置不同 - 在我们通过
this
访问computed是会触发computed的更新,其中会涉及到watcher
操作Dep
?(之间的关系是什么?)
Observe, Watch 跟 Dep
根据顺序我们分别阅读 Observe
, Watch
跟 Dep
,首先我们学习Observe
Observe
我们可以在下方先找到observe
函数,并开始代码阅读
// node_modules/vue/src/core/observer/index.js
/* @flow */
import Dep from './dep'
import VNode from '../vdom/vnode'
import { arrayMethods } from './array'
import {
def,
warn,
hasOwn,
hasProto,
isObject,
isPlainObject,
isPrimitive,
isUndef,
isValidArrayIndex,
isServerRendering
} from '../util/index'
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
/**
* In some cases we may want to disable observation inside a component's
* update computation.
*/
// 全局的Observe的标识符号,查看是否允许实例化Observe实例
export let shouldObserve: boolean = true
// 切换全局状态
export function toggleObserving (value: boolean) {
shouldObserve = value
}
/**
* 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.
*/
export class Observer {
// 对应观察者数据
value: any;
// dep对象,那么我们知道了一个Objserve绑定一个dep
dep: Dep;
// vm 对于当前观察者的引用数量
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
// 初始化数据
this.value = value
// 创建新的Dep实例对象
this.dep = new Dep()
this.vmCount = 0
// 设置当前对象的__ob__属性为当前观察者
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
/*
* 重新数组方法
* 如果存在插入对象调用objserve.observeArray(inserted)
* 无论如何一定会引发ob.dep.notify()
* 然后返回对应的操作数据
*/
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 遍历数据对每个元素创建observe
this.observeArray(value)
} else {
// 如果数据为plainObj那么对对象中的每一个属性值,进行
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
// 针对对象中的每一个Key进行可相应设置
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
// 针对每一个数据进行观察者构建
observeArray (items: Array<any>) {
for (let 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: Object) {
/* 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: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const 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.
*/
// 建立观察者对象
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 如果不是对象,或者是VNode的节点对象,是不能成为观察对象的
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
// 是否允许创建观察者的全局标识
shouldObserve &&
// 不为服务端渲染
!isServerRendering() &&
// 是数组 或者 plainObj
(Array.isArray(value) || isPlainObject(value)) &&
// 对象具备可拓展性,也就是可以插入新的属性值
Object.isExtensible(value) &&
// 并且对应的值不是一个vue的实例
!value._isVue
) {
// 创建新的观察者,注意这里的value只能是Array或者PlainObj,构造函数在上方
ob = new Observer(value)
}
// 共享当前的观察者的vm计数
if (asRootData && ob) {
ob.vmCount++
}
// 返回观察者
return ob
}
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
// 代理的数据对象
obj: Object,
// 键值
key: string,
// 数据值,在walk中未传递值
val: any,
// 自定义Setter
customSetter?: ?Function,
shallow?: boolean
) {
// 创建一个dep实例对象
const dep = new Dep()
// 获取对象的叙述属性/访问器属性
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 获取原本的访问器属性
const getter = property && property.get
const setter = property && property.set
// 如果没有get访问器 或者 有setter访问器,并且参数数量为2,那么val直接获取对象中的值
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 对属性进行观察者实例化,如果为引用类型那么就会命中
let childOb = !shallow && observe(val)
// 设置对应的访问器属性
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 获取数据
const value = getter ? getter.call(obj) : val
// 如果当前全局有Watcher的get执行时次
if (Dep.target) {
// 让Watch收集当前的dep实例对象到newDepIds跟newDeps中
// 同时也把当前的Watcher传给dep的sub数组中
// 理解为Watch 根 Dep的一个相互绑定的关系
dep.depend()
if (childOb) {
// 如果存在子观察者,子观察者也进行收集
childOb.dep.depend()
// 如果是数组,那么遍历一层观察者然后采集
if (Array.isArray(value)) {
dependArray(value)
}
}
}
// 返回数据
return value
},
set: function reactiveSetter (newVal) {
// 获取数据值
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 数据不变 或者 为NaN也不变
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
// 如果有之前的访问器,就运行访问器进行数据计算
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 如果是一个新的对象(仅当Key值没变的情况下,更新观察者)
childOb = !shallow && observe(newVal)
// 通知Watcher依次进行update方法调用
dep.notify()
}
})
}
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 如果是数组,那么重写的splice进行数据更新
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 如果原本就存在当前数据项,那么直接进行赋值即可
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && 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
}
// 相当于对新的数据进行可相应设置,并且主动触发一次notify(),用于更新当前观察者涉及的Watcher
defineReactive(ob.value, key, val)
ob.dep.notify(
return val
}
/**
* Delete a property and trigger change if necessary.
*/
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 同Set调用重写过后的方法进行操作
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && 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()
}
/**
* Collect dependencies on array elements when the array is touched, since
* we cannot intercept array element access like property getters.
*/
function dependArray (value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
阅读完Observe
以后,我们大致可以知道一个Observe
实例对象跟一个Dep
实例是一个一对一的关系,然后Observe
实例对象上每个属性跟一个Dep
实例也是1对1的关系。
Dep
接着我们来看看Dep
// node_modules/vue/src/core/observer/dep.js
/* @flow */
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
let uid = 0
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
// 全局正在执行的Watcher
static target: ?Watcher;
// dep的实例的唯一ID
id: number;
// 用于收集Watcher的数组
subs: Array<Watcher>;
// 基本初始化
constructor () {
this.id = uid++
this.subs = []
}
// 推入一个Watcher
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除指定的Watcher
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 如果当前全局有正在执行的Watch就会将当前依赖,同时在Watcher.addDep中也会调用当前dep的addSub
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 遍历执行Watcher上的update方法
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !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((a, b) => a.id - b.id)
}
for (let 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.
// 全局正在进行评估的watcher
Dep.target = null
// 评估栈
const targetStack = []
// 全局推入一个watcher|null
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
// 全局弹出
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
看完Dep
以后我们可以推断出,Dep
实际上是用于跟Watcher
建立多对多关系的,因为一个Dep
实例可以绑定多个Wacter
,一个Watcher
也可以绑定多个Dep
实例,同时我们可以看到我们在Observe
中数据发生变动后触发的notify
方法实际上主要是用于触发watcher
上的update
方法, 那么我们接着就一起看看Watcher
。
Watcher
/* @flow */
import {
warn,
remove,
isObject,
parsePath,
_Set as Set,
handleError,
invokeWithErrorHandling,
noop
} from '../util/index'
import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'
import type { SimpleSet } from '../util/index'
let uid = 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.
*/
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
// 当前的VM实例, 表明Watcher创建通常要绑定与我们的
vm: Component,
// 主执行函数
expOrFn: string | Function,
// 执行函数后的回调函数, 当前函数通常为用户自定义的watch
cb: Function,
// 配置项
options?: ?Object,
// 是否为渲染watch
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
// vm对象的渲染watch指向当前watch实例子
vm._watcher = this
}
// 推入vm的_watchers的watch列表
vm._watchers.push(this)
// options
// 初始化配置项
if (options) {
// 是否是深度监听
this.deep = !!options.deep
// 是否为用户定义的watch
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
// 判断数据是否脏了,也就是过时了
this.dirty = this.lazy // for lazy watchers
// 采集的dep列表
this.deps = []
// 新的Dep列表
this.newDeps = []
// Dep集合,保证Dep不重复手机
this.depIds = new Set()
// 新的Dep集合保证不重复采集
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
// 是否立马计算值,如果是的话
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
// 评估数据以及重新采集依赖的过程
get () {
// 首先将当前Watch推入栈站中(其实这里越看越像是Agent的感觉,运行的执行上下文 跟 执行上下文栈)
pushTarget(this)
let value
// 当前VM
const vm = this.vm
try {
// 执行更新方法
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.
*/·
// 采集dep
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
// 重新整理dep
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let 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.
*/
// 更新,这里就是主要就是dep的notify调用的位置
update () {
/* istanbul ignore else */
// 如果是lazy的,那么就先标识为数据已经过期
if (this.lazy) {
this.dirty = true
// 如果是同步任务,那么就会立刻执行
} else if (this.sync) {
this.run()
} else {
// 先加入执行Watcher的执行队列
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
// 翻译:调度任务的接口,将会在调度任务中调用当前函数
run () {
if (this.active) {
const 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
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
// 翻译:只有lazy的Watcher才会调用这个函数,computed就是一个lazy watcher
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
// 调用dep把自己给收集进去
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
// 把自己从所有的dep中移除,并且失活
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)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
- 关于页面watcher的截图
- 大致关系
那么到这里我们就完成了对于
Observe
,Dep
以及Watch
的学习。那么这里可能有小伙伴疑惑,就是我们的queueWatcher
做了什么。 那么我们接下来就来看看queueWatcher
scheduler(queueWatcher)
我们在下面找到queueWatcher
并开始阅读
/* @flow */
import type Watcher from './watcher'
import config from '../config'
import { callHook, activateChildComponent } from '../instance/lifecycle'
import {
warn,
nextTick,
devtools,
inBrowser,
isIE
} from '../util/index'
export const MAX_UPDATE_COUNT = 100
// 队列
const queue: Array<Watcher> = []
// 用于存放Keep-alive的组建
const activatedChildren: Array<Component> = []
// 是否已经存在当前watch
let has: { [key: number]: ?true } = {}
// 循环测算
let circular: { [key: number]: number } = {}
// 是否等待
let waiting = false
// 是否正在进行任务调度
let flushing = false
// 当前正在执行的watch的任务队列下标
let index = 0
/**
* Reset the scheduler's state.
*/
function resetSchedulerState () {
index = queue.length = activatedChildren.length = 0
has = {}
if (process.env.NODE_ENV !== 'production') {
circular = {}
}
waiting = flushing = false
}
// Async edge case #6566 requires saving the timestamp when event listeners are
// attached. However, calling performance.now() has a perf overhead especially
// if the page has thousands of event listeners. Instead, we take a timestamp
// every time the scheduler flushes and use that for all event listeners
// attached during that flush.
export let currentFlushTimestamp = 0
// Async edge case fix requires storing an event listener's attach timestamp.
let getNow: () => number = Date.now
// Determine what event timestamp the browser is using. Annoyingly, the
// timestamp can either be hi-res (relative to page load) or low-res
// (relative to UNIX epoch), so in order to compare time we have to use the
// same timestamp type when saving the flush timestamp.
// All IE versions use low-res event timestamps, and have problematic clock
// implementations (#9632)
if (inBrowser && !isIE) {
const performance = window.performance
if (
performance &&
typeof performance.now === 'function' &&
getNow() > document.createEvent('Event').timeStamp
) {
// if the event timestamp, although evaluated AFTER the Date.now(), is
// smaller than it, it means the event is using a hi-res timestamp,
// and we need to use the hi-res version for event listener timestamps as
// well.
getNow = () => performance.now()
}
}
/**
* Flush both queues and run the watchers.
*/
// 更新所有队列 以及 执行watcher的run
function flushSchedulerQueue () {
// 当前时间
currentFlushTimestamp = getNow()
// 开始更新
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
// 翻译:
// 刷新前排序队列。
// 这确保:
// 1. 组件从父级更新到子级。 (因为父母总是在孩子之前创建)
// 2. 组件的用户观察者在其渲染观察者之前运行(因为用户观察者在渲染观察者之前创建)
// 3. 如果一个组件在父组件的 watcher 运行期间被销毁,
// 可以跳过它的观察者。
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
// 如果有需要先执行的回调,那么先执行
if (watcher.before) {
watcher.before()
}
id = watcher.id
// 清楚状态
has[id] = null
// 执行run
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
// 调用Kep-alive组建的activated钩子,
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
// 调用组件的Update钩子
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
/**
* Queue a kept-alive component that was activated during patch.
* The queue will be processed after the entire tree has been patched.
*/
export function queueActivatedComponent (vm: Component) {
// setting _inactive to false here so that a render function can
// rely on checking whether it's in an inactive tree (e.g. router-view)
vm._inactive = false
activatedChildren.push(vm)
}
function callActivatedHooks (queue) {
for (let i = 0; i < queue.length; i++) {
queue[i]._inactive = true
activateChildComponent(queue[i], true /* true */)
}
}
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 如果当前Watch不存在,才推入队列
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
// 放在当前执行的Watcher之后执行
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
// 推入nextTick进行执行
nextTick(flushSchedulerQueue)
}
}
}
我们通过阅读以上代码可以了解到queueWatcher
将等待执行的Watcher
压入queue
并在nextTick
后回调flushSchedulerQueue
(循环调用Watch)方法。有点类似于浏览器的事件循环中的微任务检查点,我们来看看nextTick
中的具体实现吧
nextTick
/* @flow */
/* globals MutationObserver */
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// 是否用微任务
export let isUsingMicroTask = false
// 回调列表
const callbacks = []
// 是否等待
let pending = false
// 执行回调函数
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// Here we have async deferring wrappers using microtasks.
// In 2.5 we used (macro) tasks (in combination with microtasks).
// However, it has subtle problems when state is changed right before repaint
// (e.g. #6813, out-in transitions).
// Also, using (macro) tasks in event handler would cause some weird behaviors
// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
// So we now use microtasks everywhere, again.
// A major drawback of this tradeoff is that there are some scenarios
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
// 翻译:
// 这里我们有使用微任务的异步延迟包装器。
// 在 2.5 中,我们使用(宏)任务(结合微任务)。
// 然而,当状态在重绘之前改变时,它有一些微妙的问题
//(例如#6813,出入过渡)。
// 此外,在事件处理程序中使用(宏)任务会导致一些奇怪的行为
// 这是无法规避的(例如#7109、#7153、#7546、#7834、#8109)。
// 所以我们现在再次在任何地方使用微任务。
// 这种权衡的一个主要缺点是存在一些场景
// 微任务的优先级太高,应该在两者之间触发
// 顺序事件(例如 #4521、#6690,它们有变通方法)
// 甚至在同一事件的冒泡之间 (#6566)。
let timerFunc
// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
// 翻译:
// nextTick 行为利用了可以访问的微任务队列
// 通过本机 Promise.then 或 MutationObserver。
// MutationObserver 有更广泛的支持,但是它存在严重错误
// 在触摸事件处理程序中触发时,iOS 中的 UIWebView = 9.3.3。它
// 触发几次后完全停止工作...所以,如果是原生的Promise 可用,我们将使用它:
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// In problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
// 在有问题的 UIWebViews 中,Promise.then 并没有完全中断,但是
// 它可能会陷入一个奇怪的状态,回调被推入
// 微任务队列,但队列没有被刷新,直到浏览器
// 需要做一些其他的工作,例如处理一个计时器。因此我们可以
// 通过添加一个空计时器“强制”刷新微任务队列。
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
// 如果没有回调,那就返回一个Promise
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
那么到这里我们就知道nextTick
的内部实现实际上是基于Promise.then
,MutationObserve
,setImmediate
(不常见于浏览器环境),SetTimeout
连续的降级处理产生。
那么也就是说我们Watcher
更新的任务队列,回作为浏览器的微任务或者任务执行。OK那么到这里我们再回到initMixin
中的vm.$mount(vm.$options.el)
继续学习。
$mount
// node_modules/vue/src/platforms/web/runtime/index.js
/* @flow */
import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'
import {
query,
mustUseProp,
isReservedTag,
isReservedAttr,
getTagNamespace,
isUnknownElement
} from 'web/util/index'
import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'
// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
这里我们跟着mountComponent
函数进入core/instance/lifecycle
initLifecycle
我们优先查看mountComponent
函数
// node_modules/vue/src/core/instance/lifecycle.js
/* @flow */
import config from '../config'
import Watcher from '../observer/watcher'
import { mark, measure } from '../util/perf'
import { createEmptyVNode } from '../vdom/vnode'
import { updateComponentListeners } from './events'
import { resolveSlots } from './render-helpers/resolve-slots'
import { toggleObserving } from '../observer/index'
import { pushTarget, popTarget } from '../observer/dep'
import {
warn,
noop,
remove,
emptyObject,
validateProp,
invokeWithErrorHandling
} from '../util/index'
// 用于Vnode创建子组件时记录调用栈
export let activeInstance: any = null
export let isUpdatingChildComponent: boolean = false
// 切换栈,当当前组建的patch的过程中存在子组件时可以挂在到当前正在执行的vnode节点
export function setActiveInstance(vm: Component) {
const prevActiveInstance = activeInstance
activeInstance = vm
return () => {
activeInstance = prevActiveInstance
}
}
export function initLifecycle (vm: Component) {
const options = vm.$options
// 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)
}
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
}
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const 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 {
// update
// 否则就进行patch
vm.$el = vm.__patch__(prevVnode, vnode)
}
// 切换原本的Vnode确保父Vnode下可以正确挂在子组件
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
// 如果父组建是一个高阶组建,那么父组建的vnode也进行更新
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 () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
// 调用生命周期 beforeDestroy
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// remove self from parent
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
// 清空渲染相关的dep
if (vm._watcher) {
vm._watcher.teardown()
}
let 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
// 干掉DOM
vm.__patch__(vm._vnode, null)
// fire destroyed hook
// 调用beforeDestroy
callHook(vm, 'destroyed')
// turn off all instance listeners.
// 关掉event
vm.$off()
// remove __vue__ reference
// 如果有挂载元素,清空掉
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 如果没有渲染函数就挂在一个Vnode空节点的函数
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
// 调用对应的钩子
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const 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 {
// 调用vm._render()结果传入vm._update
updateComponent = () => {
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
// 构建一个新的watch
/*
* 那么这里实际上就会调用Watcher内部的get,get调用_render()
* _render是渲染方法,也就是会把对应的跟视图相关的数据进行获取,同时由于当前正在运行更新视图的Watch
* 那么此时就完成了对于依赖关系的构建完成
*/
new Watcher(vm, updateComponent, noop, {
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
}
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
目前逻辑链路大致如下
然后我们现在需要看到我们的_render()
方法,通过尤大对于_update
方法的参数定义我们得知_render
的返回值是VNode
那么我们先来看看_render()
的内部实现吧!
_render()
下方我们主要关注renderMixin
代码
// node_modules/vue/src/core/instance/render.js
/* @flow */
import {
warn,
nextTick,
emptyObject,
handleError,
defineReactive
} from '../util/index'
import { createElement } from '../vdom/create-element'
import { installRenderHelpers } from './render-helpers/index'
import { resolveSlots } from './render-helpers/resolve-slots'
import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots'
import VNode, { createEmptyVNode } from '../vdom/vnode'
import { isUpdatingChildComponent } from './lifecycle'
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)
}
}
export let currentRenderingInstance: Component | null = null
// for testing only
export function setCurrentRenderingInstance (vm: Component) {
currentRenderingInstance = vm
}
export function renderMixin (Vue: Class<Component>) {
// 注册相关VNode操作工具函数
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
// 这里我们可以看到,我们的$nextTick就是基于内部的nextTick函数实现的
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
const vm: Component = this
// 获取对应的渲染函数跟父节点的Vnode
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
// 设置父节点。这允许渲染函数访问
// 到占位符节点上的数据。
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
// 不需要维护堆栈,因为所有的渲染 fns 都会被调用
// 彼此分开。嵌套组件的渲染 fns 被调用
// 当父组件被修补时
currentRenderingInstance = vm
// vm._renderProxy = vm 这个是在initMixin中执行的
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
}
那么看完我们知道我们核心的生成Vnode的过程在render
函数中,在脚手架环境下reader
实际上在脚本获取到组件引用是就已经被webpack
中的loader进行处理了,他最终实际上调用了node_modules/vue-template-compiler/build.js
中的createCompiler
生成的compile
进行处理的,过程比较繁琐,大致原理是通过AST进行解析,然后针对不同类型的节点进行处理,并且通过vm
提供的工具函数以及vm
进行数据访问,大致render
内部情况如下:
// render()
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", { staticClass: "hello" }, [
_c("h1", [_vm._v(_vm._s(_vm.msg))]),
_vm._v(" "),
_c("button", { on: { click: _vm.onChange } }, [_vm._v("我是按钮")]),
_vm._v(
"\n " +
_vm._s(_vm.myNumber) +
"\n " +
_vm._s(_vm.doubleMyNumber) +
"\n "
),
_c("h2", [_vm._v("Essential Links")]),
_vm._v(" "),
_vm._m(0),
_vm._v(" "),
_c("h2", [_vm._v("Ecosystem")]),
_vm._v(" "),
_vm._m(1)
])
}
var staticRenderFns = [
function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("ul", [
_c("li", [
_c("a", { attrs: { href: "https://vuejs.org", target: "_blank" } }, [
_vm._v("\n Core Docs\n ")
])
]),
_vm._v(" "),
_c("li", [
_c(
"a",
{ attrs: { href: "https://forum.vuejs.org", target: "_blank" } },
[_vm._v("\n Forum\n ")]
)
]),
_vm._v(" "),
_c("li", [
_c(
"a",
{ attrs: { href: "https://chat.vuejs.org", target: "_blank" } },
[_vm._v("\n Community Chat\n ")]
)
]),
_vm._v(" "),
_c("li", [
_c(
"a",
{ attrs: { href: "https://twitter.com/vuejs", target: "_blank" } },
[_vm._v("\n Twitter\n ")]
)
]),
_vm._v(" "),
_c("br"),
_vm._v(" "),
_c("li", [
_c(
"a",
{
attrs: {
href: "http://vuejs-templates.github.io/webpack/",
target: "_blank"
}
},
[_vm._v("\n Docs for This Template\n ")]
)
])
])
},
function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("ul", [
_c("li", [
_c(
"a",
{ attrs: { href: "http://router.vuejs.org/", target: "_blank" } },
[_vm._v("\n vue-router\n ")]
)
]),
_vm._v(" "),
_c("li", [
_c(
"a",
{ attrs: { href: "http://vuex.vuejs.org/", target: "_blank" } },
[_vm._v("\n vuex\n ")]
)
]),
_vm._v(" "),
_c("li", [
_c(
"a",
{ attrs: { href: "http://vue-loader.vuejs.org/", target: "_blank" } },
[_vm._v("\n vue-loader\n ")]
)
]),
_vm._v(" "),
_c("li", [
_c(
"a",
{
attrs: {
href: "https://github.com/vuejs/awesome-vue",
target: "_blank"
}
},
[_vm._v("\n awesome-vue\n ")]
)
])
])
}
]
render._withStripped = true
var esExports = { render: render, staticRenderFns: staticRenderFns }
export default esExports
if (module.hot) {
module.hot.accept()
if (module.hot.data) {
require("vue-hot-reload-api") .rerender("data-v-469af010", esExports)
}
}
页面模版大致如下
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="onChange">我是按钮</button>
{{myNumber}}
{{doubleMyNumber}}
<h2>Essential Links</h2>
<ul>
<li>
<a
href="https://vuejs.org"
target="_blank"
>
Core Docs
</a>
</li>
<li>
<a
href="https://forum.vuejs.org"
target="_blank"
>
Forum
</a>
</li>
<li>
<a
href="https://chat.vuejs.org"
target="_blank"
>
Community Chat
</a>
</li>
<li>
<a
href="https://twitter.com/vuejs"
target="_blank"
>
Twitter
</a>
</li>
<br>
<li>
<a
href="http://vuejs-templates.github.io/webpack/"
target="_blank"
>
Docs for This Template
</a>
</li>
</ul>
<h2>Ecosystem</h2>
<ul>
<li>
<a
href="http://router.vuejs.org/"
target="_blank"
>
vue-router
</a>
</li>
<li>
<a
href="http://vuex.vuejs.org/"
target="_blank"
>
vuex
</a>
</li>
<li>
<a
href="http://vue-loader.vuejs.org/"
target="_blank"
>
vue-loader
</a>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
>
awesome-vue
</a>
</li>
</ul>
</div>
</template>
那么这个时候我们就明白我们的我们的Vnode是怎么来的,接着我们获取到我们的Vnode后应该如何进行渲染以及diff呢?这就需要我们回到我们的vm._update方法,它来自于
// node_modules/vue/src/core/instance/lifecycle.js
// 上方有完整文件,我们这里就忽略部分代码了
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const 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
// 有于$el就是对应的DOM元素,那么vm.__patch__就是我们生成DOM的核心路径
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
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.
}
这里我们看到vm._update
的核心方法是vm.__patch__
,它注册于node_modules/vue/src/platforms/web/runtime/index.js
文件,在浏览器环境下我们常用的patch在node_modules/vue/src/platforms/web/runtime/patch.js
,我们一起看看
patch
// node_modules/vue/src/platforms/web/runtime/patch.js
/* @flow */
import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
// 基础的模块函数,比如指令的处理以及ref的处理
import baseModules from 'core/vdom/modules/index'
// 平台特性相关的比如,这里主要是类,attrs,浏览器事件等
import platformModules from 'web/runtime/modules/index'
// the directive module should be applied last, after all
// built-in modules have been applied.
const modules = platformModules.concat(baseModules)
// 通过createPatchFunction创建`patch`
export const patch: Function = createPatchFunction({ nodeOps, modules })
这里我们查看createPatchFunction
来看看patch
是如何产生的, 我们直接看到下方的createPatchFunction
// node_modules/vue/src/core/vdom/patch.js
/**
* Virtual DOM patching algorithm based on Snabbdom by
* Simon Friis Vindum (@paldepind)
* Licensed under the MIT License
* https://github.com/paldepind/snabbdom/blob/master/LICENSE
*
* modified by Evan You (@yyx990803)
*
* Not type-checking this because this file is perf-critical and the cost
* of making flow understand it is not worth it.
*/
import VNode, { cloneVNode } from './vnode'
import config from '../config'
import { SSR_ATTR } from 'shared/constants'
import { registerRef } from './modules/ref'
import { traverse } from '../observer/traverse'
import { activeInstance } from '../instance/lifecycle'
import { isTextInputType } from 'web/util/element'
import {
warn,
isDef,
isUndef,
isTrue,
makeMap,
isRegExp,
isPrimitive
} from '../util/index'
export const emptyNode = new VNode('', {}, [])
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
function sameVnode (a, b) {
return (
// 循环中的Key
a.key === b.key &&
// 异步工厂函数
a.asyncFactory === b.asyncFactory && (
(
// 标签名
a.tag === b.tag &&
// 评论
a.isComment === b.isComment &&
// 是否都存在对应的数据
isDef(a.data) === isDef(b.data) &&
// 如果是input标签的话就需要有相同的type,或者久都是文本类型的,这里我搬运一下函数
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
isUndef(b.asyncFactory.error)
)
)
)
}
// 搬运函数isTextInputType
export const isTextInputType = makeMap('text,number,password,search,email,tel,url')
function sameInputType (a, b) {
if (a.tag !== 'input') return true
let i
const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
export function createPatchFunction (backend) {
let i, j
const cbs = {}
// 获取参数modules, nodeOps
const { modules, nodeOps } = backend
// 针对module中的操作进行归类
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
function emptyNodeAt (elm) {
return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
function createRmCb (childElm, listeners) {
function remove () {
if (--remove.listeners === 0) {
removeNode(childElm)
}
}
remove.listeners = listeners
return remove
}
function removeNode (el) {
const parent = nodeOps.parentNode(el)
// element may have already been removed due to v-html / v-text
if (isDef(parent)) {
nodeOps.removeChild(parent, el)
}
}
function isUnknownElement (vnode, inVPre) {
return (
!inVPre &&
!vnode.ns &&
!(
config.ignoredElements.length &&
config.ignoredElements.some(ignore => {
return isRegExp(ignore)
? ignore.test(vnode.tag)
: ignore === vnode.tag
})
) &&
config.isUnknownElement(vnode.tag)
)
}
let creatingElmInVPre = 0
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// 如果之前已经存在elm,并且存在ownerArray,那么对vnode进行深拷贝
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
// 创建组件,如果是一个组件节点的话
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
// vnode的data
const data = vnode.data
// 子孙节点
const children = vnode.children
// 标签名
const tag = vnode.tag
if (isDef(tag)) {
// 这里调用节点操作方法
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
// 设置CSS的Scoped
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// in Weex, the default insertion order is parent-first.
// List items can be optimized to use children-first insertion
// with append="tree".
const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
if (!appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
createChildren(vnode, children, insertedVnodeQueue)
if (appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
} else {
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
// 如果是组件节点的Vnode,是存初始化方法的,具体可看
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
// 翻译
// 调用 init 钩子后,如果 vnode 是子组件
// 它应该已经创建了一个子实例并挂载了它。孩子
// 组件还设置了占位符 vnode 的 elm。
// 在这种情况下,我们可以只返回元素并完成。
if (isDef(vnode.componentInstance)) {
// 做cbs的created操作
initComponent(vnode, insertedVnodeQueue)
// 插入对应的DOM节点
insert(parentElm, vnode.elm, refElm)
// 如果是Kep-alive的组件
if (isTrue(isReactivated)) {
// 如果是kep-alive组件,需要调用父组件的activate方法
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
vnode.data.pendingInsert = null
}
vnode.elm = vnode.componentInstance.$el
if (isPatchable(vnode)) {
// 通过module的内容对组件的指令,类,事件等做初始化操作
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
} else {
// empty component root.
registerRef(vnode)
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode)
}
}
function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i
// 如果外层对kepAlive的组件有transition的设置,就会触发node_modules/vue/src/platforms/web/runtime/modules/transition.js 文件enter方法
let innerNode = vnode
while (innerNode.componentInstance) {
innerNode = innerNode.componentInstance._vnode
if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
for (i = 0; i < cbs.activate.length; ++i) {
cbs.activate[i](emptyNode, innerNode)
}
insertedVnodeQueue.push(innerNode)
break
}
}
// unlike a newly created component,
// a reactivated keep-alive component doesn't insert itself
// 再次插入节点
insert(parentElm, vnode.elm, refElm)
}
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else {
nodeOps.appendChild(parent, elm)
}
}
}
function createChildren (vnode, children, insertedVnodeQueue) {
// 如果是数组就递归渲染
if (Array.isArray(children)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(children)
}
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
}
// 如果是简单类型就增加一个文本节点
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
function isPatchable (vnode) {
while (vnode.componentInstance) {
vnode = vnode.componentInstance._vnode
}
return isDef(vnode.tag)
}
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
// set scope id attribute for scoped CSS.
// this is implemented as a special case to avoid the overhead
// of going through the normal attribute patching process.
function setScope (vnode) {
let i
if (isDef(i = vnode.fnScopeId)) {
nodeOps.setStyleScope(vnode.elm, i)
} else {
let ancestor = vnode
while (ancestor) {
if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
nodeOps.setStyleScope(vnode.elm, i)
}
ancestor = ancestor.parent
}
}
// for slot content they should also get the scopeId from the host instance.
if (isDef(i = activeInstance) &&
i !== vnode.context &&
i !== vnode.fnContext &&
isDef(i = i.$options._scopeId)
) {
nodeOps.setStyleScope(vnode.elm, i)
}
}
// 循环创建新元素
function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx)
}
}
// 执行摧毁节点
function invokeDestroyHook (vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
// 依次执行摧毁操作中的方法
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
}
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
}
function removeVnodes (vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (isDef(ch)) {
if (isDef(ch.tag)) {
removeAndInvokeRemoveHook(ch)
invokeDestroyHook(ch)
} else { // Text node
removeNode(ch.elm)
}
}
}
}
function removeAndInvokeRemoveHook (vnode, rm) {
if (isDef(rm) || isDef(vnode.data)) {
let i
const listeners = cbs.remove.length + 1
if (isDef(rm)) {
// we have a recursively passed down rm callback
// increase the listeners count
rm.listeners += listeners
} else {
// directly removing
rm = createRmCb(vnode.elm, listeners)
}
// recursively invoke hooks on child component root node
if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
removeAndInvokeRemoveHook(i, rm)
}
for (i = 0; i < cbs.remove.length; ++i) {
cbs.remove[i](vnode, rm)
}
if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
i(vnode, rm)
} else {
rm()
}
} else {
removeNode(vnode.elm)
}
}
function updateChildren (
// 真实的DOM节点
parentElm,
// 老的子节点数组
oldCh,
// 新的字子点数组
newCh,
// 插入队列
insertedVnodeQueue,
removeOnly
) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
// removeOnly 是一个特殊标志,仅由<transition-group>使用
// 确保移除的元素保持在正确的相对位置
// 在离开过渡期间
const canMove = !removeOnly
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
// 开始循环
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
/*
* 如果key相同,
* tagName相同,
* Vnode.data相同,
* Vnode.isComment相同,
* 还有是一个Input类型,那么就是相同的节点
*/
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
// 直接进行调换位置的节点操作
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
// 直接进行调换位置的节点操作
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
// 如果四个位置对比都没有命中对应的节点
} else {
// 初始化一个key跟Index对应的map
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
// 在老列表中查找新的子元素
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
// 如果在原本的里面找不到,那就是一个新元素,那就需要创建一个新的元素
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
// 如果找到了
vnodeToMove = oldCh[idxInOld]
// 这里的sameVnode主要是解决oldKeyToIdx[newStartVnode.key]的匹配可能不准确的问题
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
//相同的Key不同的元素吗,那么需要创建新元素
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
// 如果是老的列表先没了,就要把剩余的新的进行节点创建
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
// 否则移除原有元素
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
function checkDuplicateKeys (children) {
const seenKeys = {}
for (let i = 0; i < children.length; i++) {
const vnode = children[i]
const key = vnode.key
if (isDef(key)) {
if (seenKeys[key]) {
warn(
`Duplicate keys detected: '${key}'. This may cause an update error.`,
vnode.context
)
} else {
seenKeys[key] = true
}
}
}
}
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
function patchVnode (
// 老节点
oldVnode,
// 新节点
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
// 如果相同,那么直接返回
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
// 拿到现在视图上的DOM元素
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
// 为静态树重用元素。
// 请注意,我们仅在 vnode 被克隆时才这样做
// 如果新节点没有被克隆,则表示渲染函数已经被克隆
// 由 hot-reload-api 重置,我们需要进行适当的重新渲染。
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
// data比如一些class或者是绑定的attr的名称
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
const oldCh = oldVnode.children
const ch = vnode.children
// 调用对应的module中的更新方法
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
// 如果当前Vnode不存在文本,也就是存在树状结构的话,那么久
if (isUndef(vnode.text)) {
// 如果都存在子节点数组
if (isDef(oldCh) && isDef(ch)) {
// 并且数组不相等的话
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
// 如果老节点是文本节点,新节点不存在文本,那么久取出文本,并且给增加子元素
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// 将老节点移除
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
// 仅将新节点的文本滞空
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
// 替换文本
nodeOps.setTextContent(elm, vnode.text)
}
// 如果有data并且data存在hook,那么调用hook
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue
} else {
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
let hydrationBailed = false
// list of modules that can skip create hook during hydration because they
// are already rendered on the client or has no need for initialization
// Note: style is excluded because it relies on initial clone for future
// deep updates (#7063).
const isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key')
// Note: this is a browser-only function so we can assume elms are DOM nodes.
function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
let i
const { tag, data, children } = vnode
inVPre = inVPre || (data && data.pre)
vnode.elm = elm
if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
vnode.isAsyncPlaceholder = true
return true
}
// assert node match
if (process.env.NODE_ENV !== 'production') {
if (!assertNodeMatch(elm, vnode, inVPre)) {
return false
}
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
if (isDef(i = vnode.componentInstance)) {
// child component. it should have hydrated its own tree.
initComponent(vnode, insertedVnodeQueue)
return true
}
}
if (isDef(tag)) {
if (isDef(children)) {
// empty element, allow client to pick up and populate children
if (!elm.hasChildNodes()) {
createChildren(vnode, children, insertedVnodeQueue)
} else {
// v-html and domProps: innerHTML
if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {
if (i !== elm.innerHTML) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!hydrationBailed
) {
hydrationBailed = true
console.warn('Parent: ', elm)
console.warn('server innerHTML: ', i)
console.warn('client innerHTML: ', elm.innerHTML)
}
return false
}
} else {
// iterate and compare children lists
let childrenMatch = true
let childNode = elm.firstChild
for (let i = 0; i < children.length; i++) {
if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) {
childrenMatch = false
break
}
childNode = childNode.nextSibling
}
// if childNode is not null, it means the actual childNodes list is
// longer than the virtual children list.
if (!childrenMatch || childNode) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!hydrationBailed
) {
hydrationBailed = true
console.warn('Parent: ', elm)
console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
}
return false
}
}
}
}
if (isDef(data)) {
let fullInvoke = false
for (const key in data) {
if (!isRenderedModule(key)) {
fullInvoke = true
invokeCreateHooks(vnode, insertedVnodeQueue)
break
}
}
if (!fullInvoke && data['class']) {
// ensure collecting deps for deep class bindings for future updates
traverse(data['class'])
}
}
} else if (elm.data !== vnode.text) {
elm.data = vnode.text
}
return true
}
function assertNodeMatch (node, vnode, inVPre) {
if (isDef(vnode.tag)) {
return vnode.tag.indexOf('vue-component') === 0 || (
!isUnknownElement(vnode, inVPre) &&
vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
)
} else {
return node.nodeType === (vnode.isComment ? 8 : 3)
}
}
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// 是否未定义
if (isUndef(vnode)) {
// 如果命中就摧毁当前老的节点
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
// 如果老节点没有定义
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
// 根据当前vnode创建DOM
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
// patchVnode 完成后
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
// 如果是真实的DOM节点
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
// 最终这边会循环调用,vnode的data的insert方法
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
// 返回DOM元素
return vnode.elm
}
}
了解完patch
的过程,那么我们整个初始化的基本上就了解完成。但是关于Vue2
我们还有很多还没学习的地方,我们就在下一篇中进行比较常见的地方进行学习。