Skip to main content

Core

Vue Constructor

core/instance/index.js:

// 从五个文件导入五个方法(不包括 warn)
import { warn } from '../util/index'
import { eventsMixin } from './events'
import { initMixin } from './init'
import { lifecycleMixin } from './lifecycle'
import { renderMixin } from './render'
import { stateMixin } from './state'

// 定义 Vue 构造函数
function Vue(options) {
this._init(options)
}

// 将 Vue 作为参数传递给导入的五个方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

// 导出 Vue
export default Vue

core/instance/init.js:

// initMixin(Vue)
Vue.prototype._init = function (options?: object) {}

core/instance/state.js:

// stateMixin(Vue)
Vue.prototype.$data = data
Vue.prototype.$props = props
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: object
): Function {}

core/instance/events.js:

// eventsMixin(Vue)
Vue.prototype.$on = function (
event: string | Array<string>,
fn: Function
): Component {}
Vue.prototype.$once = function (event: string, fn: Function): Component {}
Vue.prototype.$off = function (
event?: string | Array<string>,
fn?: Function
): Component {}
Vue.prototype.$emit = function (event: string): Component {}

core/instance/lifecycle.js:

// lifecycleMixin(Vue)
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}
Vue.prototype.$forceUpdate = function () {}
Vue.prototype.$destroy = function () {}

core/instance/render.js:

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)
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
const parentData = parentVnode && parentVnode.data
}

core/instance/render-helpers/index.js:

export function installRenderHelpers(target: any) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
}

Vue Prototype

core/index.js:

import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
import { initGlobalAPI } from './global-api/index'
import Vue from './instance/index'

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering,
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
get() {
return this.$vnode && this.$vnode.ssrContext
},
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext,
})

Vue.version = '__VERSION__'

export default Vue

runtime/index.js:

import config from 'core/config'
import Vue from 'core/index'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser, isChrome } from 'core/util/index'
import { extend, noop } from 'shared/util'

import {
getTagNamespace,
isReservedAttr,
isReservedTag,
isUnknownElement,
mustUseProp,
query,
} from 'web/util/index'

import platformComponents from './components/index'
import platformDirectives from './directives/index'
import { patch } from './patch'

// Install platform specific utils.
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// Install platform runtime directives & components.
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// Install platform patch function.
Vue.prototype.__patch__ = inBrowser ? patch : noop

// Public mount method.
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}

platforms/web/entry-runtime-with-compiler.js 重写 Vue.prototype.$mount 方法:

import config from 'core/config'
import { cached } from 'core/util/index'

import { compileToFunctions } from './compiler/index'
import Vue from './runtime/index'
import {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
} from './util/compat'
import { query } from './util/index'

const mount = Vue.prototype.$mount

const idToTemplate = cached((id) => {
const el = query(id)
return el && el.innerHTML
})

/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
function getOuterHTML(el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}

Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)

if (el === document.body || el === document.documentElement) {
// Do not mount Vue to <html> or <body>
return this
}

const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#')
template = idToTemplate(template)
} else if (template.nodeType) {
template = template.innerHTML
} else {
// Invalid template option
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
const { render, staticRenderFns } = compileToFunctions(
template,
{
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments,
},
this
)
options.render = render
options.staticRenderFns = staticRenderFns
}
}

return mount.call(this, el, hydrating)
}

Vue.compile = compileToFunctions

export default Vue

Global API

core/global-api/index.js 添加 Vue.XXX 静态方法:

// initGlobalAPI
Vue.config = config
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive,
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
Vue.options = {
components: {
KeepAlive,
// Transition 和 TransitionGroup 组件在 runtime/index.js 文件中被添加
// Transition,
// TransitionGroup
},
directives: Object.create(null),
// 在 runtime/index.js 文件中, 为 directives 添加了两个平台化的指令 model 和 show
// directives:{
// model,
// show
// },
filters: Object.create(null),
_base: Vue,
}

// initUse: global-api/use.js
Vue.use = function (plugin: Function | object) {}

// initMixin: global-api/mixin.js
Vue.mixin = function (mixin: object) {}

// initExtend: global-api/extend.js
Vue.cid = 0
Vue.extend = function (extendOptions: object): Function {}

// initAssetRegisters: global-api/assets.js
Vue.component
= Vue.directive
= Vue.filter
= function (
id: string,
definition: Function | object
): Function | object | void {}

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext,
})

Vue.version = '__VERSION__'

// entry-runtime-with-compiler.js
Vue.compile = compileToFunctions

Global Extend API

core/global-api/extend.js:

  • Vue.extend/vm.$options._base.extend will return brand new Vue constructor.
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})

if (cachedCtors[SuperId])
return cachedCtors[SuperId]

const name = extendOptions.name || Super.options.name
const Sub = function VueComponent(options) {
this._init(options)
}

Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(Super.options, extendOptions)
Sub.super = Super

// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props)
initProps(Sub)

if (Sub.options.computed)
initComputed(Sub)

// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use

// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach((type) => {
Sub[type] = Super[type]
})

// enable recursive self-lookup
if (name)
Sub.options.components[name] = Sub

// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)

// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}

Global NextTick API

为了减少布局和渲染, VueDOM 更新设计为异步更新, 每次侦听到数据变化, 将开启一个队列, 并缓冲在同一事件循环中发生的所有数据变更。 如果同一个 watcher 被多次触发, 只会被推入到队列中一次。 在下一个事件循环 tick 中, Vue 才会真正执行队列中的数据变更, 页面才会重新渲染, 使得多次 DOM 更新合并成一次批处理更新。

core/util/next-tick.js:

import { noop } from 'shared/util'
import { isIOS, isNative } from './env'
import { handleError } from './error'

const callbacks = []
let pending = false

function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0

for (let i = 0; i < copies.length; i++) copies[i]()
}

let microTimerFunc
let macroTimerFunc
let useMacroTask = false

// setImmediate -> MessageChannel -> setTimeout.
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (
typeof MessageChannel !== 'undefined'
&& (isNative(MessageChannel)
// PhantomJS
|| MessageChannel.toString() === '[object MessageChannelConstructor]')
) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}

// Promise.then -> Macro Timer.
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
if (isIOS)
setTimeout(noop)
}
} else {
microTimerFunc = macroTimerFunc
}

/**
* Wrap a function so that if any code inside triggers state change,
* the changes are queued using a (macro) task instead of a micro task.
*/
export function withMacroTask(fn: Function): Function {
return (
fn._withTask
|| (fn._withTask = function (...args) {
useMacroTask = true
const res = fn(...args)
useMacroTask = false
return res
})
)
}

export function nextTick(cb?: Function, ctx?: object) {
let _resolve

callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})

if (!pending) {
pending = true

if (useMacroTask)
macroTimerFunc()
else microTimerFunc()

if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve) => {
_resolve = resolve
})
}
}
}

Global Mixin API

core/global-api/mixin.js:

export function initMixin(Vue: GlobalAPI) {
Vue.mixin = function (mixin: object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}

Global Use API

core/global-api/use.js:

export function initUse(Vue: GlobalAPI) {
Vue.use = function (plugin: Function | object, ...args: any) {
const installedPlugins
= this._installedPlugins || (this._installedPlugins = [])

if (installedPlugins.includes(plugin))
return this

// Pass `Vue` to plugin install hook.
args.unshift(this)

if (typeof plugin.install === 'function')
plugin.install(...args)
else if (typeof plugin === 'function')
plugin(...args)

installedPlugins.push(plugin)
return this
}
}