本文已参与「新人创作礼」活动,一起开启掘金创作之路。
接上一节,继续分析new Vue()过程:
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
$mount过程
上一节我们分析了data的init过程,在init过程结束后,执行如下代码:
if (vm.$options.el) {
// initData之后 该挂载了
vm.$mount(vm.$options.el);
}
这个$mount在哪定义的呢,在platforms/web/runtime/index.js中有定义
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// el转换成DOM 这里el是 <div id="app">{{ message }}</div>
el = el && inBrowser ? query(el) : undefined
// 执行mountComponent函数
return mountComponent(this, el, hydrating)
}
如果是compiler版本的vue,可以自定义template(我们以这个版本来进行分析),则有如下代码:
// 缓存上面代码的$mount函数
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 查找元素 这里返回<div id="app">{{ message }}</div>
el = el && query(el)
/* istanbul ignore if */
// 元素不能是body或html
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
// 拿到options
const options = this.$options
// resolve template/el and convert to render function
// render函数 手写render函数则直接跳过这个逻辑, 如果没有写render函数,则分析template,进行编译生成render函数
if (!options.render) {
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) {
template = getOuterHTML(el)
}
// 存在template
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
// 进行编译 生成render函数
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// options中生成了属性render函数
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
return mount.call(this, el, hydrating)
}
/**
* Query an element selector if it's not an element already.
*/
function query (el) {
if (typeof el === 'string') {
// 如果el不是元素,是字符串,则在页面中查找这个元素并返回否则报错
var selected = document.querySelector(el);
if (!selected) {
"development" !== 'production' && warn(
'Cannot find element: ' + el
);
return document.createElement('div')
}
return selected
} else {
return el
}
}
这里的操作主要就是将template转换成render函数进行后续操作,其中的编译过程较为麻烦,笔者在后面会出文章进行分析。 接下来执行上面的代码后,最后执行了mountComponent函数,定义在src/core/instance/lifecycle.js中,源码如下:
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
// 这里的el是 <div id="app">{{ message }}</div>
vm.$el = el
if (!vm.$options.render) {
// 如果还没有render,则报错了,必须要有render函数
vm.$options.render = createEmptyVNode
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
/* istanbul ignore if */
// 性能埋点相关 跳过 执行else逻辑
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函数,这是大佬
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// 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
// 实例化watcher,这里叫渲染watcher(RenderWatcher),为渲染而生的watcher
// 注意传入的参数,updateComponent是第二个参数,noop是个空函数
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
// 这里是根实例没有 $vnode,所以在实例化Watcher之后,执行mounted生命周期函数
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
这里面主要是定义了一个updateComponent方法,然后实例化了一个渲染watcher,实例化的过程中干了什么呢,现在来看看这个Watcher的真面目,源码在src/core/observer/watcher.js中:
/* @flow */
let uid = 0
/**
* 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;
computed: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
dep: Dep;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component, // Vue
expOrFn: string | Function, // 这里是updateComponent
cb: Function, // 空函数noop
options?: ?Object, // before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } }
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
// 添加了_watcher属性指向自身,renderWatcher专有属性
vm._watcher = this
}
// 前面定义过的_watchers
vm._watchers.push(this)
// options // before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate')
// 在这次分析中不用看
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.computed = !!options.computed
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.computed = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.computed // for computed watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
// 上面我没注释的都不用关注, 这里的expOrFn是updateComponent,赋值给this.getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
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
)
}
}
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
// 执行这个get,代码在下面
this.value = this.get()
}
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// pushTarget这个是收集依赖的,这次分析我们不用关注
pushTarget(this)
let value
const vm = this.vm
try {
// 看这儿,我们执行了this.getter,也就是执行了 updateComponent函数
value = this.getter.call(vm, vm)
} 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
}
}
那updateComponent函数到底发生了什么呢?点击此处前往下一节