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 = thisvm._self = vm// 这些代码之后再讲initLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(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.$optionsif (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 updateComponentupdateComponent = () => {const name = vm._nameconst id = vm._uidconst 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 = thisconst { render, _parentVnode } = vm.$optionslet vnodetry {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
