new Vue 发生了什么?
const app = new Vue({
template: '
hello world',el: '#app'
})
Vue实际上是一个类
function Vue (options) {
if (!(this instanceof Vue)) {
// 必须通过new来调用
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 注意options参数,是创建实例时候传入的对象
this._init(options)
}
可以看到 Vue
只能通过 new 关键字初始化,然后会调用 this._init
方法。
Vue.prototype._init = function (options) {
const vm = this
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')
// 先看这行代码
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
先看$mount
// 先缓存之前的$mount方法
const mount = Vue.prototype.$mount
// 再重写$mount方法
Vue.prototype.$mount = function (el) {
const options = this.$options
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render // render很重要,在后面会用到
options.staticRenderFns = staticRenderFns
}
return mount.call(this, el, hydrating) // 开始挂载
}
注意compileToFunctions是把template转换成render函数,怎么转的看下下面
const compiler = require('vue-template-compiler')
let str = `
999`
console.log(compiler.compile(str))
发现包含ast和render这两个属性。注意这个render我们之后会用到
{
ast: {
type: 1,
tag: 'div',
attrsList: [],
attrsMap: { 'v-if': 'msg' },
rawAttrsMap: {},
parent: undefined,
children: [ [Object] ],
if: 'msg',
ifConditions: [ [Object] ],
plain: true,
static: false,
staticRoot: false,
ifProcessed: true
},
render: `with(this){return (msg)?_c('div',[_v("999")]):_e()}`,
staticRenderFns: [],
errors: [],
tips: []
}
之前的$mount方法
Vue.prototype.$mount = function (el) {
el = el && query(el)
return mountComponent(this, el)
}
终于来到了重点mountComponent方法
export function mountComponent () {
callHook(vm, 'beforeMount')
let updateComponent
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)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
}
mountComponent 核心就是先实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法,在此方法中调用 vm._render 方法先生成虚拟 Node,最终调用 vm._update 更新 DOM。
Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数,这块儿我们会在之后的章节中介绍。
Vue 的 _render
方法,它用来把实例渲染成一个虚拟 Node。
Vue.prototype._render = function () {
const vm = this
const { render, _parentVnode } = vm.$options
let vnode
try {
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {}
}
这段代码最关键的是 render
方法的调用,我们在平时的开发工作中手写 render
方法的场景比较少,而写的比较多的是 template
模板,在之前的 mounted
方法的实现中,会把 template
编译成 render
方法,但这个编译过程是非常复杂的,我们不打算在这里展开讲,之后会专门花一个章节来分析 Vue 的编译过程。
在 Vue 的官方文档中介绍了 render
函数的第一个参数是 createElement
render: function (createElement) {
return createElement('div', {
attrs: {
id: 'app'
},
}, this.message)
}
再回到 _render
函数中的 render
方法的调用:
vnode = render.call(vm._renderProxy, vm.$createElement)
可以看到,render
函数中的 createElement
方法就是 vm.$createElement
方法。实际上,vm.$createElement
方法定义是在执行 initRender
方法的时候。
export function initRender (vm) {
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)
}
结合我们之前的render