Vue2 源码解读三:Vue实例
文章目录Vue2 源码解读一:Global APIVue2 源码解读二:mergeOptionsVue2 源码解读三:new Vue 1、入口方法 2、initMixin 3、stateMixin 4、eventsMixin 5、lifecycleMixin 6、renderMixinVue2 源码解读三:new Vue在创建Vue应用时,都会用到初始化Vue实例对象的方法new Vu
文章目录
Vue2 源码解读一:Global API
Vue2 源码解读二:mergeOptions
Vue2 源码解读三:Vue实例
1、入口方法
2、initMixin
3、stateMixin
4、eventsMixin
5、lifecycleMixin
6、renderMixin
Vue2 源码解读四:Observer模块
Vue2 源码解读三:Vue实例
在创建Vue应用时,都会用到初始化Vue实例对象的方法
new Vue(options)
。接下来,就看看在创建Vue实例对象时,Vue2源码都做了些什么?
1、入口文件
先看一下创建Vue时的入口文件src/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'
// 定义Vue
function Vue (options) {
// 必须是Vue的实例
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 调用Vue初始化方法(在init.js中定义的)
this._init(options)
}
// 给Vue实例添加_init方法
initMixin(Vue)
// 给Vue实例添加 state相关的属性和方法
stateMixin(Vue)
// 给Vue实例添加$on、$off、$once、$emit
eventsMixin(Vue)
// 给Vue实例添加生命周期的主动触发方法
lifecycleMixin(Vue)
// 给Vue实例添加渲染函数和nextTick钩子函数
renderMixin(Vue)
export default Vue
从以上源码可以看出,先定义function Vue,然后给这个Vue的原型属性prototype上添加一些属性和方法,以$开头的是暴露给用户使用,以_开头的是Vue内部使用。new Vue时调用_init初始化方法。
initMixin(Vue)
:该方法在Vue的原型属性prototype上添加_init
初始化方法;stateMixin(Vue)
:该方法在Vue的原型属性prototype上添加$data
、$props
数据属性,添加$set
响应式设置、$del
响应式删除、$watch
添加建建ring器方法;eventsMixin(Vue)
:该方法在Vue的原型属性prototype上添加$on
挂载监听方法、$once
只监听一次的监听方法、$off
卸载监听方法、$emit
暴露监听方法;lifecycleMixin(Vue)
:该方法在Vue的原型属性prototype上添加_update
、$forceUpdate
强制更新视图方法、$destroy
销毁Vue实例函数;renderMixin(Vue)
:该方法在Vue的原型属性prototype上添加$nextTick
等待dom更新完成的钩子函数、_render
渲染函数。
大概可以分为4类:
- 实例 property:vm.$data、vm.$props、vm.$el、vm.$options、vm.$parent、vm.$root、vm.$children、vm.$slots、vm.$scopedSlots、vm.$refs、vm.$isServer、vm.$attrs、vm.$listeners 。 使用详情请看官方说明
- 实例方法 / 数据:vm.$watch、vm.$set、vm.$delete。 使用详情请看官方说明
- 实例方法 / 事件:vm.$on、vm.$once、vm.$off、vm.$emit。 使用详情请看官方说明
- 实例方法 / 生命周期:vm.$mount、vm.$forceUpdate、vm.$nextTick、vm.$destroy。 使用详情请看官方说明
2、initMixin
该方法在init.js
里面,整个js文件所做的都是给Vue实例添加_init方法。
/* @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'
// 全局的自增ID
let uid = 0
export function initMixin (Vue: Class<Component>) {
// Vue初始化时的方法
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid 自增ID
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
// _isVue 不会被监听
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
// 优化内部组件
initInternalComponent(vm, options)
} else {
// 合并options
vm.$options = mergeOptions(
// 解析vm的构造函数
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
// 使用Proxy代理_renderProxy
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 初始化声明周期
initLifecycle(vm)
// 初始化事件
initEvents(vm)
// 初始化渲染函数
initRender(vm)
// 调用beforeCreate钩子
callHook(vm, 'beforeCreate')
// 初始化Injections
initInjections(vm) // resolve injections before data/props
// 初始化state
initState(vm)
// 初始化Provide
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)
}
// 挂载el
if (vm.$options.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
// render函数处理
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
// 处理super,来自于extend
if (Ctor.super) {
// 递归处理super
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
// super发生变化,采用新的
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
// 检查更新的options
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// 合并options
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
// 来自于extend
const sealed = Ctor.sealedOptions
// 对比Ctor.options和Ctor.sealedOptions,统计更新
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
该方法大致的流程如下:
- 添加_uid属性,标志Vue实例的唯一性;添加_isVue属性,标记属性不被监听。
- 如果是组件,则优化内部组件
initInternalComponent()
,否则合并options给Vue的$options属性。 - 添加_renderProxy属性,优先使用Proxy数据劫持,然后对get、set、has方法做一些逻辑判断提示。
initLifecycle(vm)
:初始化生命周期,对生命周期的相关属性设置默认值,比如:_isMounted、_watcher、_inactive、_isDestroyed等等。initEvents(vm)
:初始化事件,对事件的相关属性设置默认值,比如:_events、_hasHookEvent。initRender(vm)
:初始化渲染函数,对渲染函数的相关属性设置默认值。比如_vnode,$slots、_c(内部调用的模板编译渲染函数)、$createElement(用户调用的渲染函数)等等- 触发beforeCreate钩子函数
callHook(vm, 'beforeCreate')
initInjections(vm)
:初始化inject,给inject所有属性添加监听,变成响应式。initState(vm)
:初始化props、methods、data、computed、watch等。给他们添加监听,变成响应式。initProvide(vm)
:初始化provide。- 触发created钩子函数
callHook(vm, 'created')
。从beforeCreate到created,主要是给Vue实例中的数据添加监听,变成响应式。 - 挂载$el
vm.$mount(vm.$options.el)
以上的初始化方法、callHook以及挂载,后续会继续详解。
3、stateMixin
给Vue实例添加 state相关的属性和方法
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.
// 定义空的data
const dataDef = {}
// 指向到_data,在initState时定义的
dataDef.get = function () { return this._data }
// 定义空的prop
const propsDef = {}
// 指向到_props,在initProps时定义的
propsDef.get = function () { return this._props }
// set禁用提示
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)
}
}
// 把dataDef给$data,并添加劫持
Object.defineProperty(Vue.prototype, '$data', dataDef)
// 把propsDef给$props,并添加劫持
Object.defineProperty(Vue.prototype, '$props', propsDef)
// $set等同于全局的Vue.set
Vue.prototype.$set = set
// $delete等同于全局的Vue.delete
Vue.prototype.$delete = del
// 定义$watch方法
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
// 如果cb是一个对象,则把对象使用$watch解析
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
// 创建监听器Watcher对象
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果指定了immediate,立即触发cb回调
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
// 从所有依赖项的dep列表中删除自身watcher
watcher.teardown()
}
}
}
4、eventsMixin
给Vue实例添加$on、$off、$once、$emit事件相关方法
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
/** 监听当前实例上的自定义事件 */
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
// 数组处理,其实就是循环调用$on,添加监听
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// 添加到事件列表_event
(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
// 优化 hook:event。监听生命周期的钩子触发
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
/** 监听一个自定义事件,但是只触发一次 */
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
// 移除事件
vm.$off(event, on)
// 调用回调函数
fn.apply(vm, arguments)
}
on.fn = fn
// 添加监听
vm.$on(event, on)
return vm
}
/** 移除自定义事件监听器 */
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
// 如果没有传递参数,则移除所有的事件监听器
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
// 数组处理,其实就是循环调用$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
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
// 只移除该事件的该回调函数的监听器
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
/** 触发当前实例上的事件 */
Vue.prototype.$emit = function (event: 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
}
}
5、lifecycleMixin
给Vue实例添加生命周期的主动触发方法
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.
}
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
// 调用_watcher的update函数,通知页面更新
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
// 从parent中移除自己
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
// 卸载watcher
if (vm._watcher) {
vm._watcher.teardown()
}
// 卸载所有的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
// 从当前渲染中树销毁
vm.__patch__(vm._vnode, null)
// fire destroyed hook
// 触发destroyed钩子
callHook(vm, 'destroyed')
// turn off all instance listeners.
// 移除所有监听器
vm.$off()
// remove __vue__ reference
// 移除—__vue__关联
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
6、renderMixin
给Vue实例添加渲染函数和nextTick钩子函数
/* @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'
/** 初始化reader函数 */
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') {
// 只读的$attrs
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
// 只读的$listeners
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
// 只读的$attrs
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
// 只读的$listeners
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>) {
// install runtime convenience helpers
// 安装运行时的helper
installRenderHelpers(Vue.prototype)
// $nextTick调用全局的nextTick函数
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) {
// 规范化ScopedSlots
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.
// 设置父节点vnode,允许通过$vnode访问数据
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.
// 不需要使用堆栈,因为所有render方法都是单独调用的。当父组件被patch的时候,会调用嵌套组件的render函数。
// 当前正在渲染的实例
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 */
// _vnode来自于实例挂载、或者更新的时候
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
// 非VNode
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
vnode = createEmptyVNode()
}
// set parent
// 设置parentVNode
vnode.parent = _parentVnode
return vnode
}
}
更多推荐
所有评论(0)