vue实例挂载的实现,也就是执行 vm.$mount 的方法
在 Runtime + Compiler 版本,入口文件是: src/platform/web/entry-runtime-with-compiler.js
$mount 方法也是在这个文件中被定义的
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 首先对于传入的这个 el 参数,做了一个处理,可以看到 el 参数,可以是个字符串,也可以是个 Element
// 这里调用了query方法,query方法实际上就是原生的方法,有则document.querySelector,无则,document.createElement 返回一个div 等处理
el = el && query(el)
// 拿到这个 el 以后,这里就已经被转化成了这个 dom 对象,然后它又做了一个简单的判断
// 也就是说我的 el 如果是 body 或者是 html 标签的话,它就会报一个错
// 就是 vue 不可以直接挂载到这个 body 或者 html 上,因为它是会覆盖的,你挂载的话,你会把整个body覆盖,那整个HTML文档不对了
// 所以说这里在开发环境下,就报了这个警告
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to or - mount to normal elements instead.`
)
return this
}
// 拿到这个options。判断有没有定义render方法,因为平时开发的过程中,代码都是脚手架生成,会有一个 render
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
// 再次判断你有没有写template,一般模板默认是有index.html的,在组件内部也可以使用 template, 有则进行处理
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// 如果没有 templete 则执行 getOuterHTML 拿到html
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
// 这一块就是跟编译相关了, 编译的话,它其实是调用这个 compileToFunction
// 拿到生成的一个 render 函数,还有 staticRenderFns
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// 这个 options.render 会在渲染 vnode 的时候会用到
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
// 调用 mount, 这个mount 是 runtimeOnly 时的 $mount
return mount.call(this, el, hydrating)
}
它首先获得了 Vue.prototype.$mount
方法,用这个 mount 变量缓存起来,然后又重新定义了一遍这个方法
回到最初的定义,在 src/platforms/web/runtime/index.js 中,是最原始的定义
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
setTimeout(() => {
if (config.devtools) {
if (devtools) {
devtools.emit('init', Vue)
} else if (
process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test'
) {
console[console.info ? 'info' : 'log'](
'Download the Vue Devtools extension for a better development experience:\n' +
'https://github.com/vuejs/vue-devtools'
)
}
}
if (process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test' &&
config.productionTip !== false &&
typeof console !== 'undefined'
) {
console[console.info ? 'info' : 'log'](
`You are running Vue in development mode.\n` +
`Make sure to turn on production mode when deploying for production.\n` +
`See more tips at https://vuejs.org/guide/deployment.html`
)
}
}, 0)
}
在我们的这个入口, 为什么会重新定义一遍 $mount?
实际上, 上面这块最原始的代码是给 RuntimeOnly 版本复用用的一个函数
在 Runtime + Compiler 版本,在 src/core/instance/init.js 的 initMixin 中
执行 vm.$mount 时候,实际上调的就是 src/platform/web/entry-runtime-with-compiler.js 这个入口文件
中的 Vue.prototype.$mount 这个函数,现在回到入口文件中的代码中,查看相关代码上的注释
在执行了入口文件中的 .$mount, 最终调用了 RuntimeOnly 的 $mount, 最终执行 mountComponent 方法
而 mountComponent 是在 src/core/instance/lifecycle.js 中定义的
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
// 首先会把这个 el 的dom 被 vm.$el 缓存起来
vm.$el = el
// 判断有没有 render 函数(或没有 template转换来的 render), 没有则创建一个空的 vnode, 并在开发环境警告
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
// 这个警告就是写了 template,但是没有使用含有编译的版本,或者两者都没有写
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
// 这里先执行 beforeMount 的钩子函数,跳过
callHook(vm, 'beforeMount')
let updateComponent
// 在dev环境上是更多做了性能埋点相关的处理,当性能比较卡顿时,可以利用这些东西,看文档就行,跳过
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
// 这里才是我们需要关注的
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// 这个函数就是调用了 vm._update,第一个参数是通过 vm._render渲染出来一个vnode, 第二个参数理解为 false 就可以了
// 之后,调用 new Watcher 这里实际上是 渲染 watcher
// 因为 watcher 这个东西其实是跟响应式原理强相关的一个类
// 它实际上就是一个观察者模式,它其实有很多自定义 watcher,也会有一个叫渲染 watcher
// 调用 new Watcher 的的时候,三个参数,第一个vm, 第二个 updateComponent函数, 第三个 noop(空函数), 第四个配置对象,第五个布尔值
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */) // 注意这里第5个参数是 true, 表示是一个渲染watcher
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean // 是否是渲染 watcher
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this) // 收集watcher
// 这里忽略这个options
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// 注意这里,这里就是调用方传进来的 updateComponent
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
// 这里有依赖收集,相关的
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) // 这里会调用 getter, 也就是上层调用方传进来的 updateComponent 方法,就会执行方法内部的 update
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}