前言
A: 爲什麼要做源碼解讀?
Q: 我們新到一個環境,第一件事情就是熟悉環境熟悉項目,這個很考驗閱讀源碼的能力以及耐心。vue是個很好的庫,知名度高,對js的學習具有向上性,所以搞清楚邏輯是有好處的。
A: 閱讀源碼的程度?
Q: 我們完全沒必要從頭到尾細細品味,只需要知道一些核心的實現就好了,畢竟vue也算是個產品,我們沒必要搞清楚一個產品,我們只需要知道產品的核心就夠了,多餘的也是業務代碼。(相對來說)
開始
Vue應用的實例化以及掛載
main.js
new Vue({ // 初始化vue實例
//components: { App } // vue1.0的寫法
render: h => h(App) // 最先執行,返回一個符合component的對象,vue2.0的寫法
})
.$mount('#app') // 掛載vue實例到 ‘#app’
render函數
render: h => h(App)
就是
render:function(h){
return h(App)
}
即
render: function (createElement) {
return createElement(App)
}
找到Vue引用源文件(debug順序)
import Vue from 'vue'
找到
node-modules/vue
打開package.json 找到
"main": "dist/vue.runtime.common.js"
"main"是 npm模塊曝光的主要文件.
打開vue.runtime.common.jsctrl/command + a, ctrl/command + k, ctrl/command + 1
,快捷鍵把所有方法摺疊
你會發現 update執行了2次,我明明只初始化了一次vue實例,爲什麼update2次了呢?原因在下方表明。
構造函數
拉到最下面,
module.exports = Vue;
導出的是一個Vue構造函數。
當前文件搜索 Vue 找到 構造函數
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options); // 調用 初始化方法
}
初始化函數
var uid$3 = 0;
vm._uid = uid$3++; //每個vue實例 擁有唯一id,從0開始 ++
// 合併初始化vue實例的參數(入參options和默認參數)
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
initLifecycle();// 初始化實例生命週期相關的參數
使用Object.create(null)用來獲取一個沒有原型鏈的對象類型
initEvents(); // 初始化實例事件觸發相關的參數
initRender(); // 初始化實例渲染相關的參數
//create的準備工作做好了,觸發beforeCreate的生命週期
callHook(vm, 'beforeCreate');
initState(); // 初始化實例狀態
// 狀態也初始化好了,觸發create的生命週期,所以 create和 breforCreate的區別就在 create的時候 有狀態。
callHook(vm, 'created');
至此,vue實例的初始化完成,然後掛載到節點
掛載實例到節點
// 將mount('#app') => query('#app') 查找到dom對象,賦值給vue.$el
// 觸發beforeMount的生命週期,所以beforeMount 和 create的區別就在 beforeMount的時候 有掛載節點。
callHook(vm, 'beforeMount');
// 拿到當前將要掛載的Vnode(虛擬dom對象)
vm._render() vm.$vnode
// (更新)渲染頁面
vm._update(vm._render(), hydrating);
vm.__patch__();
createElm(); // 按照虛擬dom生成真實dom
createElm函數
// 這是一個遞歸方法,vue實例的初始化是創建一個根節點,然後再將render函數傳入的組件掛載,這就是流程圖執行2次update的原因。
// 已經刪減,只留主要邏輯,判斷虛擬dom 的 tag屬性
if (isDef(tag)) {
{
// 遍歷虛擬dom的子節點,並且創建,然後遞歸當前方法。
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
insert(parentElm, vnode.elm, refElm);
}
}
// 如果沒有子節點的,直接創建dom,然後插入
else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text);
insert(parentElm, vnode.elm, refElm);
} else {
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
// 觸發Mount的生命週期
callHook(vm, 'mounted');
至此就是 Vue根節點初始化掛載和渲染的流程.
Vue數據更新
首先 我們改造下 app.vue,像官網一樣,我們新增一個雙向綁定的文本框。
現在我們知道了,第一次的update只是掛載了根節點,那麼我們新增了文本框的組件其實是在第二次init的時候初始化的。
我們可以着重看第二次的流程,搞清楚,數據的監聽與更新。
initData函數
function initData (vm) {
var data = vm.$options.data;
// 把從render函數返回的data函數對象賦值給data,然後data.call(this,this),也就是vm.data();拿到data返回值
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
// observe data
observe(data, true /* asRootData */);
}
function getData (data, vm) {
pushTarget();
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, "data()");
return {}
} finally {
popTarget();
}
}
function observe (value, asRootData) {
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value); // 返回一個 新建的 Observer
}
return ob
}
Observer對象(監聽者)
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
var augment = hasProto
? protoAugment
: copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value);
}
};
// 如果是對象,則按照key來劫持
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
};
// 如果是數組,就遍歷每個數組元素,再每個元素再判斷是否爲數組,對象
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
數據劫持
Watcher對象(訂閱者)
小結
未完待續。。。