new Vue 發(fā)生了什么?

const app = new Vue({template: 'hello world',el: '#app'})
Vue實際上是一個類
function Vue (options) {if (!(this instanceof Vue)) {// 必須通過new來調(diào)用warn('Vue is a constructor and should be called with the `new` keyword')}// 注意options參數(shù),是創(chuàng)建實例時候傳入的對象this._init(options)}
可以看到?Vue?只能通過 new 關鍵字初始化,然后會調(diào)用?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轉(zhuǎn)換成render函數(shù),怎么轉(zhuǎn)的看下下面
const compiler = require('vue-template-compiler')let str = `999`console.log(compiler.compile(str))
發(fā)現(xiàn)包含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 核心就是先實例化一個渲染W(wǎng)atcher,在它的回調(diào)函數(shù)中會調(diào)用 updateComponent 方法,在此方法中調(diào)用 vm._render 方法先生成虛擬 Node,最終調(diào)用 vm._update 更新 DOM。
Watcher 在這里起到兩個作用,一個是初始化的時候會執(zhí)行回調(diào)函數(shù),另一個是當 vm 實例中的監(jiān)測的數(shù)據(jù)發(fā)生變化的時候執(zhí)行回調(diào)函數(shù),這塊兒我們會在之后的章節(jié)中介紹。
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?方法的調(diào)用,我們在平時的開發(fā)工作中手寫?render?方法的場景比較少,而寫的比較多的是?template?模板,在之前的?mounted?方法的實現(xiàn)中,會把?template?編譯成?render?方法,但這個編譯過程是非常復雜的,我們不打算在這里展開講,之后會專門花一個章節(jié)來分析 Vue 的編譯過程。
在 Vue 的官方文檔中介紹了?render?函數(shù)的第一個參數(shù)是?createElement
render: function (createElement) {return createElement('div', {attrs: {id: 'app'},}, this.message)}
再回到?_render?函數(shù)中的?render?方法的調(diào)用:
vnode = render.call(vm._renderProxy, vm.$createElement)可以看到,render?函數(shù)中的?createElement?方法就是?vm.$createElement?方法。實際上,vm.$createElement?方法定義是在執(zhí)行?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)}
結(jié)合我們之前的render
