我的博客地址
在 Vue 內部,有一段這樣的代碼: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的原型上面掛載方法。node
initMixin 函數express
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( 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 方法中,實現了一系列的初始化操做,包括生命週期流程以及響應式系統流程的啓動。api
stateMixin 函數數組
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) } } 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
。app
eventsMixin 函數async
export function eventsMixin (Vue: Class<Component>) { const hookRE = /^hook:/ // $on的實現:在註冊時把回調函數收集起來,在觸發時將收集的事件依次調用 Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { 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 < 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: 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<string>, fn?: Function): Component { 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 < l; i++) { vm.$off(event[i], fn) } 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 < l; i++) { invokeWithErrorHandling(cbs[i], vm, args, vm, info) } } 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
。ide
lifecycleMixin 函數函數
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 { // 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
。ui
renderMixin 函數
export function renderMixin (Vue: Class<Component>) { // 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 < copies.length; i++) { capies[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
。