vue實例化都幹了什麼

本文將結合例子進行一步步講解,例子也會從簡單到複雜逐步提高,這樣理解的更深入html

<div id="app"></div>
複製代碼
const app = new Vue({
    template: '<div>child</div>',
})
app.$mount('#app');
複製代碼

建立實例

首先先調用new Vue建立了一個實例,在core/instance/index中定義了Vue的構造函數node

function Vue(options) {
    this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
複製代碼

在該文件中定義了Vue構造函數,而且經過下面幾個Mixin方法,在Vue原型上也定義了一些方法,爲何不用class由於class沒有prototype這麼靈活。web

Mixin方法 方法 屬性
initMixin _init -
stateMixin $set$delete$watch $data$props
eventsMixin $on$off$once$emit -
lifecycleMixin _update$forceUpdate$detory -
renderMixin _render$nextTick -

調用_init方法

方法在core/instance/init.js中,api

let uid = 0;
Vue.prototype._init = function (options) {
    const vm = this;
    vm._uid = uid++;
    vm._isVue = true;
    if (options && options._isComponent) {
        initInternalComponent(vm, options)
    } else {
        vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
        )
    }
    vm._renderProxy = vm;
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callhook(vm, 'beforeCreate');
    initInjections(vm);
    initState(vm);
    initProvide(vm),
    callHook(vm, 'created')
}
複製代碼

屬性:app

  1. _uid: 實例的惟一標識
  2. _isVue: 表示當前是Vue的實例
  3. $options: 合併後的options
  4. _renderProxy: 實例自己
  5. _self: 實例自己 方法:
  6. initLifecyle: 初始化自身屬性,若是當前組件是子組件,就在當前組件的父組件的$children中加入本身
  7. initEvents: 用於父子組件事件通訊初始化,以下子組件child進行和父組件事件通訊的初始化,並在vm._events對應的事件名稱加入這個函數
    <div class="parent">
        <child @change="changeToDo"></child>
    </div>
    複製代碼
  8. initRender:初始化自身屬性,同時初始化渲染函數_c$createElement。在Vue原型上添加屬性$attrs$listeners,並讓這些屬性進行響應式監聽
  9. callHook: 觸發聲明週期
  10. initInjections: 處理injectinject可以向子孫後代注入一個依賴,無論組件層次有多深
  11. initState: 初始化propsmethodsdatacomputedwatch。讓數據響應式就是這個階段完成的,watchcomputed都會生成對應的Watcher
  12. initProvide: 初始化provide,用於接受inject傳入的數據

$mount

建立實例後,會調用_init進行一系列的初始化操做,而後調用$mount$mount在不一樣平臺有不一樣的定義,以web爲例ide

Vue.$prototype.$mount = function (el) {
    el = el && query(el);
    const options = this.$options;
    if (!options.render) {
        let template = options.template
    }
    if (template) {
        const { render, staticRenderFns } = compileToFunction(template, {
            // ...
        })
        options.render = render;
        options.staticRenderFns = staticRenderFns
    }
    return mount.call(this, el);
}
複製代碼

在不一樣的平臺調用不一樣的編譯方式最後把template編譯爲render函數。而後返回調用了mount函數,最終調用的是mountComponent函數

// cores/instance/lifecycle.js
function mountComponent(vm, el) {
    vm.$el = el;
    callHook(vm, 'beforeMount');
    updateComponent = () => {
        vm._update(vm._render())
    }
    new Watcher(vm, updateComponent, noop, {
        before() {
            if (vm._isMounted && !vm._isDestoryed) {
                callHook(vm, 'beforeUpdate')
            }
        }
    })
    if (vm.$vnode == null) {
        vm._isMounted = true;
        callHook(vm, 'mounted')
    }
}
複製代碼

首先聲明瞭回調函數upateComponent,而後建立了渲染watcher,渲染watcher在初始化的時候就會執行回調函數updateComponentupdateComponent內部調用了_render_update。這兩個方法在文章開頭的renderMixinlifecycleMixin中定義了,_render用於生成vnode_update調用patch:具體的path可參照這篇文章Vue 源碼patch過程詳解,把vnode中定義的內容渲染到真實DOM中,最後調用mounted鉤子。oop

更改data

把上面的例子進行更改,當templatedata發生了更改,再看看具體的變化。post

new Vue({
    template: '<div class="parent" @click="change">{{visible}}</div>'
    data: {
        return {
            visible: 'all'
        }
    },
    methods: {
        change() {
            this.visible = 'change';
        }
    }
})
複製代碼

當咱們點擊元素的時候,就會觸發change事件更改data中定義的值visible學習

數據響應式

initState中會對data中定義的值進行響應式設置

//core/instance/state.js
function initData(vm) {
    let data = vm.$options.data;
    data = vm._data = typeof data === 'function' 
        ? getData(data, vm)
        : data || {}
    observe(data, true)
}
複製代碼

這裏在初始化data的時候,首先調用了自己,獲得返回的值,而後調用observe進行數據響應式具體的可參照這篇文章深刻源碼學習Vue響應式原理。回到mountComponent中,在建立renderWatcher的時候首先會執行一遍updateComponent,進行依賴收集

數據更新

當數據更新後,依賴該data數據的watcher就會更新,這裏只有renderWatcher有依賴,因此這個watcher就會調用回調函數,從新執行一遍_render_updatevm._render根據template生成的render來生成vnode

// core/instance/render.js
Vue.prototype._render = function {
    const vm = this;
    const { render, _parentVnode } = vm.$options;
    if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
            _parentVnode.data.scopedSlots,
            vm.$slots,
            vm.$scopedSlots,
        )
    }
    vm.$vnode = _parentVnode;
    let vm.$vnode = _parentVnode
    let vnode
    try {
        currentRenderingInstance = vm;
        vnode = render.call(vm._renderProxy, vm.$createElement)
    } finally {
        currentRenderingInstance = null
    }
    if (Array.isArray(vnode) && vnode.length === 1) {
        vnode = vnode[0]
    }
    vnode.parent = _parentVnode
    return vnode;
}
複製代碼

能夠看到經過調用render函數最後生成了vnode, $createElement也在當前文件夾中定義過,最後生成vnode而後調用_update執行patch操做,把修改後的數據反映到真實DOM

子組件建立

對上面的例子在進行擴展,建立一個子組件

Vue.component('child', {
    template: '<div class="child">child</div>'
})
new Vue({
    template: '<div class="parent"><child></child></div>'
})
複製代碼

這裏聲明瞭一個子組件,而且父組件中調用了這個子組件,首先compileToFunctions將其編譯爲對應的render函數上面把new Vue中聲明的template編譯爲以下的render函數

ƒ anonymous(
) {
with(this){return _c('div',{staticClass:"parent"},[_c('child')],1)}
}
複製代碼

當執行當前render就會生成以下的vnode

當執行patch的時候,當發現vnode下面有children就會對children進行一系列操做。

Vue.component

回到Vue.component聲明子組件,當調用Vue.component都發生了什麼,方法定義在core/global-api/assets.js

Vue.component = function (id, definition) {
    definition.name = definition.name || id;
    definition = this.options._base.extend(definition)
    this.options[type + 's'][id] = definition;
}
複製代碼

this._options._base就是Vue構造函數,至關於調用的是Vue.extend,而後生成的definition掛載到this.options.components上,屬性名爲childVue.extend的方法定義在

Vue.extend = function (extendOptions) {
    const Super = this;
    const Sub = function VueComponent(options) {
        this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    // ...
    return Sub;
}
複製代碼

能夠看到返回是一個繼承Vue的構造函數,而且建立實例的實例也會調用Vue_init函數

patch

具體的邏輯能夠參照Vue 源碼patch過程詳解 回到父組件的$mount操做,當建立渲染watcher的時候,會當即執行updateComponent,而後內部會執行_update函數,能夠執行patch操做,而後上面圖片能夠看到children中存在值,就會走到createChildrenchildren中的元素調用createElm。由於child是子組件就會走到 createComponent而且二返回true,在內部調用鉤子initinit 鉤子函數具體實現以下:

const componentVNodeHooks = {
    init: (vnode) => {
        const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)
        child.$mount(vnode.elm)
    }
}
複製代碼

這裏就會調用createComponentInstanceForVnode函數,這個函數實際調用的就是前面在Vue.extend中返回的繼承於Vue的構造函數,最後在調用$mount函數。因此父子組件在渲染的時候鉤子執行的前後順序就是 父beforeMounted => 子beforeMounted => 子mounted => 父mounted

相關文章
相關標籤/搜索