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的原型上面挂载方法。
initMixin 函数
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 方法中,实现了一系列的初始化操作,包括生命周期流程以及响应式系统流程的启动。
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
。eventsMixin 函数
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
。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
。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
。
相关推荐
源码zanqunet 2020-10-28
anchongnanzi 2020-09-21
CRMCRMCRMDS 2020-08-12
Cricket 2020-07-27
yuzhu 2020-11-16
85477104 2020-11-17
KANSYOUKYOU 2020-11-16
sjcheck 2020-11-03
怪我瞎 2020-10-28
gloria0 2020-10-26
王军强 2020-10-21
学习web前端 2020-09-28
QiaoranC 2020-09-25
安卓猴 2020-09-12
Macuroon 2020-09-11
kiven 2020-09-11
LittleCoder 2020-09-11
Cheetahcubs 2020-09-13
小焊猪web前端 2020-09-10