个人博客地址
在 Vue 内部,有一段这样的代码:
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)
) {
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)
上面5个函数的作用是在Vue的原型上面挂载方法。
initMixin 函数
export function initMixin (Vue: Class
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(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
// 初始化操作
vm._self = vm
initLifecycle(vm)
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)
}
}
}
可以看到在 initMixin 方法中,实现了一系列的初始化操作,包括生命周期流程以及响应式系统流程的启动。
stateMixin 函数
export function stateMixin (Vue: Class
// 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)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
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 || {}
options.user = true
const 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()
}
}
}
当 stateMixin 被调用时,往Vue的原型上了挂载了三个方法:$delete
、$set
、$watch
。
eventsMixin 函数
export function eventsMixin (Vue: Class
const hookRE = /^hook:/
// $on的实现:在注册时把回调函数收集起来,在触发时将收集的事件依次调用
Vue.prototype.$on = function (event: string | Array
const vm: CompOnent= this
// 当event为数组时,遍历event将其中的每一项都调用$on
// 当event为字符串时,向事件列表中添加回调
// vm._enevts是专门用来存储事件,在initMixin中生成:vm._events = Object.create(null)
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i
}
} 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: string, fn: Function): Component {
const vm: CompOnent= this
// 当第一次触发自定义事件时,会移除这个事件监听器,然后手动运行fn函数
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$off = function (event?: string | Array
const vm: CompOnent= this
// all
// 当参数为空时,直接清空vm._events,移除所有事件监听器
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
// 当event为数组时,遍历event每一项都调用$off
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i
}
return vm
}
// specific event
const cbs = vm._events[event]
// 如果事件列表里面没有这个方法,直接返回
if (!cbs) {
return vm
}
// 如果回调函数不存在,移除该事件的所有监听器
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler
// 从vm._events中删除这个事件监听器
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
// $emit的实现:使用事件名event从vm._events中取出事件监听器的回调函数列表
// 依次执行列表中的回调函数并且把参数传入监听器回调函数
Vue.prototype.$emit = function (event: string): Component {
const vm: CompOnent= this
if (process.env.NODE_ENV !== 'production') {
const 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}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i
}
}
return vm
}
}
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
}
当 eventsMixin 函数被调用时,往 Vue 的原型上挂载了4个方法:$on
、$once
、$off
、$emit
。
lifecycleMixin 函数
export function lifecycleMixin (Vue: Class
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
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._watcher就是Vue.js实例的watcher,手动执行watcher的update方法
// 然后组件内部就会重新生成vnode,和旧的vnode进行对比,更新视图
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
}
// 调用钩子函数:beforeDestory
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
// 从watcher监听的所有状态的依赖列表中移除watcher
if (vm._watcher) {
vm._watcher.teardown()
}
// 在Watcher类中有这样一行代码: vm._watchers.push(this)
// 移除所有用户通过$watch创建的watcher实例
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
// 在vnode树上触发destory钩子函数解绑指令
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
}
}
}
当 lifecycleMixin 被调用时,往 Vue 的原型上挂载了三个方法:_updata
、$forceUpdate
、$destory
。
renderMixin 函数
export function renderMixin (Vue: Class
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
const vm: CompOnent= this
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.
currentRenderingInstance = vm
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
}
}
// 简化版的nextTick
// 用来存放回调函数事件
let callbacks = []
// 表示是否已经添加到微任务列表中
let pending = false
// 遍历callbacks,清空callbacks,依次调用回调函数
function flushCallbacks () {
penging = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i
}
}
// 把事件添加到微任务列表中去
let microTimerFunc
let p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
}
function nextTick (cb?: Function, ctx?: Object) {
// 一进来先向callback中添加回调事件
callbacks.push(() => {
if (cb) {
cb.call(ctx)
}
})
// 如果pending为false,则表示是第一次执行nextTick,将其添加到微任务中
// 如果pending为true,表示之前微任务列表中已经添加了这个方法,直接退出
if (!pending) {
pending = true
microTimerFunc()
}
}
当 renderMixin 被调用时,在 Vue 的原型上挂载了两个方法:$nextTick
、_render
。