Vue 编译器中的宏定义处理:__VUE_OPTIONS_API__等全局标志的替换与代码消除
大家好,今天我们来深入探讨 Vue 编译器中一个重要的优化环节:宏定义处理。具体来说,我们将着重分析 __VUE_OPTIONS_API__ 等全局标志的替换与代码消除,理解它们在 Vue.js 不同构建版本和功能特性支持中的作用。
Vue.js 为了适应不同的使用场景和提供更小的包体积,采用了多种构建版本。这些构建版本通常通过不同的宏定义标志来区分,从而实现代码的按需编译和消除。宏定义本质上是在编译时进行文本替换,并根据替换结果进行条件编译,这对于优化性能和控制代码体积至关重要。
宏定义在 Vue.js 中的作用
在 Vue.js 的编译过程中,宏定义主要用于以下几个方面:
- 特性开关: 启用或禁用某些特性,比如 Options API 的支持、Composition API 的支持、devtools 的支持等。
- 环境判断: 区分开发环境和生产环境,从而在不同环境下执行不同的代码逻辑,例如添加调试信息、进行性能优化等。
- 代码消除: 根据宏定义的值,在编译时消除不必要的代码,减小最终的包体积。
- 平台适配: 针对不同的平台(如 Web、Weex、小程序)编译不同的代码。
常见的 Vue.js 宏定义标志
以下是一些常见的 Vue.js 宏定义标志,以及它们的作用:
| 宏定义标志 | 作用 |
|---|---|
__VUE_OPTIONS_API__ |
决定是否支持 Options API。如果为 true,则编译包含 Options API 相关的代码;否则,移除相关代码。这使得 Vue.js 可以提供仅支持 Composition API 的构建版本,从而减小包体积。 |
__VUE_PROD_DEVTOOLS__ |
决定是否支持生产环境下的 Devtools。通常在生产环境下,为了安全和性能考虑,会禁用 Devtools。这个标志用于控制是否包含 Devtools 相关的代码。 |
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ |
决定是否在生产环境下提供 hydration mismatch 的详细信息。 Hydration mismatch 是指服务器端渲染的 HTML 结构与客户端渲染的 HTML 结构不一致的情况。提供详细信息有助于调试,但会增加包体积。 |
__VUE_SSR_COMPILE__ |
决定是否启用服务器端渲染(SSR)的编译优化。如果为 true,则编译器会针对 SSR 进行优化,例如避免不必要的 DOM 操作。 |
__VUE_NODE_TRANSFORM_CJS_ESM__ |
用于转换 CommonJS 模块为 ES 模块。在一些构建流程中,需要将 CommonJS 模块转换为 ES 模块,以便更好地进行 tree-shaking 等优化。 |
__VUE_RUNTIME_COMPILE__ |
决定是否支持运行时编译。如果为 true,则可以在运行时将模板字符串编译成渲染函数。这会增加包体积,但允许动态生成组件。 |
__FEATURE_SUSPENSE |
决定是否支持 Suspense 组件。Suspense 允许在组件挂起时显示一个占位符,直到异步操作完成。 |
__FEATURE_TELEPORT |
决定是否支持 Teleport 组件。Teleport 允许将组件渲染到 DOM 树的其他位置。 |
__COMPAT__ |
决定是否启用兼容模式。兼容模式允许 Vue.js 3 兼容 Vue.js 2 的一些 API。 |
宏定义的实现原理
Vue.js 的编译器通常使用 Rollup 或 Webpack 等构建工具,这些工具提供了插件机制,可以自定义编译流程。宏定义的实现通常依赖于这些工具的插件,这些插件会在编译过程中扫描代码,查找宏定义标志,并根据标志的值进行替换和条件编译。
例如,Rollup 提供了 rollup-plugin-replace 插件,可以用于替换代码中的字符串。Vue.js 的构建流程中可能会使用这个插件来替换宏定义标志。
// rollup.config.js
import replace from '@rollup/plugin-replace';
export default {
// ...
plugins: [
replace({
__VUE_OPTIONS_API__: false,
__VUE_PROD_DEVTOOLS__: false,
'process.env.NODE_ENV': JSON.stringify('production')
})
]
};
上面的代码片段展示了如何使用 rollup-plugin-replace 插件来替换 __VUE_OPTIONS_API__、__VUE_PROD_DEVTOOLS__ 和 process.env.NODE_ENV 这三个宏定义标志。
代码消除的实现原理
代码消除通常通过条件编译来实现。在 Vue.js 的代码中,会使用 if 语句或三元运算符来判断宏定义标志的值,从而决定是否执行某段代码。
例如:
if (__VUE_OPTIONS_API__) {
// Options API 相关的代码
console.log('Using Options API');
// ...
} else {
// 如果不支持 Options API,则执行其他代码
console.log('Options API is not supported');
}
在编译过程中,如果 __VUE_OPTIONS_API__ 的值为 false,那么编译器会将 if 语句中的代码块移除,只保留 else 语句中的代码块。这样就实现了代码消除的目的。
更复杂的情况,代码消除可能涉及到整个函数或者模块的移除。例如,如果 __VUE_OPTIONS_API__ 为 false,整个 Options API 相关的模块可能都会被移除。
实例分析:__VUE_OPTIONS_API__ 的代码消除
我们以 __VUE_OPTIONS_API__ 为例,详细分析一下代码消除的实现过程。
假设 Vue.js 的源代码中包含以下代码:
// 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)
) {
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
以及
// src/core/instance/init.js
import { initState } from './state'
import { callHook, mountComponent } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, isReservedTag, def } from '../util/index'
import config from '../config'
import { mark, measure } from '../util/perf'
let uid = 0
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
const vm = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-init:${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') {
def(vm, '$el', {
get: () => vm._vnode && vm._vnode.elm
})
}
// 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) {
mark(endTag)
measure(`vue-perf-init:${vm._uid}`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
function initInternalComponent (vm, options) {
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.listeners = vnodeComponentOptions.listeners
opts.parent = options.parent
opts._parentVnode = parentVnode
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
export function resolveConstructorOptions (Ctor) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super options have changed.
Ctor.superOptions = superOptions
// check if there are any late-modified/added options.
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
}
}
return options
}
function resolveModifiedOptions (Ctor) {
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
}
// src/core/state.js
import { isObject, def, warn } from '../util/index'
import { arrayMethods } from './array'
import {
traverse
} from '../observer/traverse'
import Dep from '../observer/dep'
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
/**
* By default, when a reactive property is set, the new value is
* also converted to become reactive. However when we are mutating a
* reactive array we do not want to trigger another deep observe on the
* new value. This flag allows us to avoid it when necessary.
*/
export let shouldObserve = true
export function initState (vm) {
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)
}
}
function initProps (vm, propsOptions) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can be non-reactive.
// const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
// keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttr(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
def(props, key, {
enumerable: true,
configurable: true,
get: () => value,
set: () => {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Prop name: "${key}"`,
vm
)
}
})
} else {
def(props, key, {
enumerable: true,
configurable: true,
get: () => value,
set: noop
})
}
// defineReactive(props, key, value)
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
function initMethods (vm, methods) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (methods[key] == null) {
warn(
`Method "${key}" has an undefined value 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 property. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
}
}
function initData (vm) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:n' +
'For example:n' +
'return {n' +
' data: function () {n' +
' return { ... }n' +
' }n' +
'}n',
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
observe(data, true /* asRootData */)
}
const computedWatcherOptions = { lazy: true }
function initComputed (vm, computed) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just normal watchers except
// that they has no setter.
for (const key in computed) {
const userDef = computed[key]
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
)
}
// create internal watcher for the computed property.
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)) {
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)
}
}
}
}
/**
* Define computed property on instance.
*/
export function defineComputed (target, key, userDef) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: 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)
}
function initWatch (vm, watch) {
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,
expOrFn,
handler,
options
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
export function stateMixin (Vue) {
// flow somehow has problems with directly declared reactive objects
// when subtype-of any is used.
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,
cb,
options
) {
const vm = 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()
}
}
}
// src/core/render.js
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) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // cached static sub-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
// render functions written by user.
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 = null
// for testing only
export function setCurrentRenderingInstance (vm) {
currentRenderingInstance = vm
}
export function renderMixin (Vue) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = nextTick
Vue.prototype._render = function () {
const vm = this
const {
render,
_parentVnode
} = vm.$options
if (vm._isMounted) {
// if the parent didn't update, the slot may be stale.
// Force update $slots and $scopedSlots manually.
for (const key in vm.$slots) {
// $flow-disable-line
vm.$slots[key] = vm.$slots[key] // force update
}
if (_parentVnode && _parentVnode.data.scopedSlots) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
}
vm.$scopedSlots = normalizeScopedSlots(
vm.$options._scopedSlots,
vm.$slots,
vm.$scopedSlots
)
// set parent instance to currenly rendering instance (for component-level injections)
setCurrentRenderingInstance(vm)
try {
// render self
currentRenderingInstance = vm
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 infinite loop
vm._vnode = vm._vnode || createEmptyVNode()
} finally {
currentRenderingInstance = null
}
// if the returned vnode is guaranteed to be a child component, we can
// optimize it to skip unnecessary patching.
if (vm._vnode && vm._vnode.parent) {
vm._vnode.parent = vm.$parent._vnode
}
return vm._vnode
}
}
// src/core/events.js
import {
warn,
invokeWithErrorHandling
} from '../util/index'
import {
defineReactive,
isPlainObject
} from '../observer/index'
export function initEvents (vm) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached listeners
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
let target
function add (event, fn) {
target.$on(event, fn)
}
function remove (event, fn) {
target.$off(event, fn)
}
function createOnceHandler (event, fn) {
const _this = target
return function onceHandler () {
const res = fn.apply(null, arguments)
if (res !== null) {
_this.$off(event, onceHandler)
}
}
}
export function updateComponentListeners (
vm,
listeners,
oldListeners
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
function updateListeners (
on,
oldOn,
add,
remove,
createOnceHandler,
vm
) {
let name, def, cur, old, event
for (name in on) {
def = on[name]
cur = oldOn[name]
if (isUndef(def)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${name}": got ` + String(def),
vm
)
} else if (isUndef(cur)) {
if (isUndef(def.fns)) {
event = createFnInvoker(def, vm)
on[name] = event
add(name, event)
} else {
// existing invoker, call hooks again
on[name] = cur
on[name].fns = def
add(name, cur)
}
} else if (isDifferent(def, cur)) {
oldOn[name] = def
update(def, cur, createOnceHandler, vm)
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
def = oldOn[name]
remove(name, def)
}
}
}
const isUndef = v => v === undefined || v === null
function isDifferent (a, b) {
const aIsFn = isFn(a)
const bIsFn = isFn(b)
if (aIsFn || bIsFn) {
return !((aIsFn && bIsFn) || (!aIsFn && !bIsFn)) || String(a) !== String(b)
} else {
return true
}
}
function isFn (v) {
return typeof v === 'function'
}
function update (def, cur, createOnceHandler, vm) {
let i, l
const oldFns = cur.fns
const fns = cur.fns = Array.isArray(def)
? def.slice()
: [def]
const add = createOnceHandler(def, vm)
for (i = 0; i < fns.length; i++) {
const fn = fns[i]
if (isUndef(oldFns.indexOf(fn))) {
add(fn)
}
}
for (i = 0; i < oldFns.length; i++) {
const fn = oldFns[i]
if (isUndef(fns.indexOf(fn))) {
remove(fn)
}
}
}
function createFnInvoker (fns, vm) {
function invoker () {
const fns = invoker.fns
if (Array.isArray(fns)) {
const cloned = fns.slice()
for (let i = 0; i < cloned.length; i++) {
invokeWithErrorHandling(cloned[i], null, arguments, vm, `v-on handler`)
}
} else {
// return handler return value for single handlers
return invokeWithErrorHandling(fns, null, arguments, vm, `v-on handler`)
}
}
invoker.fns = fns
return invoker
}
export function eventsMixin (Vue) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event, fn) {
const vm = this
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, fn) {
const vm = this
function on () {
vm.$off(event, on)
fn.apply(this, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$off = function (event, fn) {
const vm = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
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) {
const vm = this
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? Array.from(cbs) : cbs
const args = Array.from(arguments).slice(1)
for (let i = 0, l = cbs.length; i < l; i++) {
try {
invokeWithErrorHandling(cbs[i], vm, args, vm, `event handler for "${event}"`)
} catch (e) {
handleError(e, vm, `event handler for "${event}"`)
}
}
}
return vm
}
}
// src/core/lifecycle.js
import config from '../config'
import { callHook, activateChildComponent } from '../vdom/helpers/lifecycle'
import {
warn,
nextTick,
devtools,
inBrowser,
isIE
} from '../util/index'
import {
patch
} from './vdom/patch'
import Watcher from './observer/watcher'
import { pushTarget, popTarget } from './observer/dep'
import { resolveSlots } from './render-helpers/resolve-slots'
import {
mark,
measure
} from '../util/perf'
let uid = 0
export function initLifecycle (vm) {
const options = vm.$options
// locate first non-abstract parent.
let parent = options.parent
if
更多IT精英技术系列讲座,到智猿学院