Vue 原始碼解析(例項化前) – 初始化全域性API(二)

熱情的劉大爺發表於2019-01-17

前言

在前面的兩章,介紹了Vue 原始碼解析(例項化前) – 初始化全域性API(一)Vue 原始碼解析(例項化前) – 響應式資料的實現原理

由於資料雙向繫結的內容會比較多一些,而且涉及到的知識點也比較多,所以我當時就從初始化全域性API裡面單獨拿了一章出來,去講解 vue 到底是如何實現的資料雙向繫結,現在,接著把之前沒有講完的初始化全域性API要做的事情,全都給講完。

還是那句老話,如果覺得寫的哪裡不對了,還希望大家多多指出,歡迎評論;

如果覺得不錯的話,點點關注,點點贊,謝謝大家,你們的支援,是我繼續寫下去的動力?

正文

Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;
Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
Vue.options[type + 's'] = Object.create(null);

});
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents);
initUse(Vue);
initMixin$1(Vue);
initExtend(Vue);
initAssetRegisters(Vue);
複製程式碼

這篇文章,講的就是 initGlobalAPI 函式剩下的這一部分。


設定物件屬性(set)

Vue.set = set;
複製程式碼

該屬性,就是用來設定物件的屬性,如果屬性不存在,則新增新屬性並觸發更改通知。

function set (target, key, val) { 
if (isUndef(target) || isPrimitive(target) ) {
warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));

} if (Array.isArray(target) &
&
isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val
} if (key in target &
&
!(key in Object.prototype)) {
target[key] = val;
return val
} var ob = (target).__ob__;
if (target._isVue || (ob &
&
ob.vmCount)) {
warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' );
return val
} if (!ob) {
target[key] = val;
return val
} defineReactive(ob.value, key, val);
ob.dep.notify();
return val
}複製程式碼

set 方法,接收三個引數, target (目標)、 key (屬性)、val (值);

function isUndef (v) { 
return v === undefined || v === null
}function isPrimitive (value) {
return ( typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean' )
}複製程式碼

一進來,就先檢查,當前的目標是不是上面的這幾種型別,如果是的話,是沒有辦法去做類似根據對應的 key 去做值更新的;

這裡其實最主要檢查的就是當前的 target 是不是物件或者陣列,可是作者在這裡,做了除物件和陣列以外所有型別的判斷,不知道這樣做的目的是什麼,為什麼不直接判斷陣列和物件呢,那樣需要執行的程式碼也會比較少,如果大家知道為什麼要這麼做,歡迎評論。

if (Array.isArray(target) &
&
isValidArrayIndex(key)) {
}複製程式碼

檢查當前的 target 是不是陣列,並且 key 是不是有效的陣列索引:

function isValidArrayIndex (val) { 
var n = parseFloat(String(val));
return n >
= 0 &
&
Math.floor(n) === n &
&
isFinite(val)
}複製程式碼

這裡接收到的其實就是陣列的 key ,先把 key 轉成字串,然後通過 parseFloat 去格式化它;

然後去看 n 是不是大於0的正整數,並且不是無窮大的。

target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val複製程式碼

如果是陣列,並且該索引也是合法的,那麼用接收到的 val 去替換當前索引為 key 的索引項;

if (key in target &
&
!(key in Object.prototype)) {
target[key] = val;
return val
}複製程式碼

這裡要判斷的是如果當前的 target 是物件,並且存在當前 key , 並且當前 key 不是在 Object 的原型上的,那麼就用接收到新的 val 去替換當前的值;

var ob = (target).__ob__;
複製程式碼

__ ob __ 屬性是我們在列印 vue 的例項化物件時經常看到的,這個屬性其實就是在例項化 Observer 的時候,呼叫 def 函式去給當前目標新增的一個屬性:

// Observer部分var Observer = function Observer (value) { 
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);

} else {
copyAugment(value, arrayMethods, arrayKeys);

} this.observeArray(value);

} else {
this.walk(value);

}
};
// def部分function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val, enumerable: !!enumerable, writable: true, configurable: true
});

}複製程式碼

在這裡對 Observer 不做太多的講解,因為現在主要講的是 set 部分,在往下講,可能會比較亂,大家先自己看一下,看不懂的話,在接下來講例項化 vue 部分的時候在仔細對 vue 的每一個重要的 api 做詳細的講解。

_isVue 和 vmCount 在 Vue 原始碼解析(例項化前) – 資料雙向繫結的實現原理 這裡有做過講解,大家不瞭解這兩個值是幹什麼的,可以來這裡看一下。

if (!ob) { 
target[key] = val;
return val
}複製程式碼

如果當前的目標沒有 __ ob __屬性,那麼就直接做賦值,並且返回;

defineReactive(ob.value, key, val);
ob.dep.notify();
複製程式碼

這裡在上面的連結裡面,就可以看到具體做了哪些事情,這裡就不浪費篇幅去寫了;

最後直接返回當前值。

這裡總結一下,其實就是如果是存在值的話,直接更新值,如果不存在的話,就通過 defineReactive 去繫結一下複製程式碼

刪除物件屬性(del)

Vue.delete = del;
複製程式碼

這個方法就是在刪除屬性並在必要時觸發更改。

function del (target, key) { 
if (process.env.NODE_ENV !== 'production' &
&
(isUndef(target) || isPrimitive(target)) ) {
warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target))));

} if (Array.isArray(target) &
&
isValidArrayIndex(key)) {
target.splice(key, 1);
return
} var ob = (target).__ob__;
if (target._isVue || (ob &
&
ob.vmCount)) {
process.env.NODE_ENV !== 'production' &
&
warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' );
return
} if (!hasOwn(target, key)) {
return
} delete target[key];
if (!ob) {
return
} ob.dep.notify();

}複製程式碼

這個其實和 set 做的事情差不多,只是修改變成了刪除,這裡就不多講了。

nextTick

function nextTick(cb, ctx) { 
var _resolve;
callbacks.push(function () {
if (cb) {
try {
cb.call(ctx);

} catch (e) {
handleError(e, ctx, 'nextTick');

}
} else if (_resolve) {
_resolve(ctx);

}
});
if (!pending) {
pending = true;
if (useMacroTask) {
macroTimerFunc();

} else {
microTimerFunc();

}
} if (!cb &
&
typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;

})
}
}複製程式碼

callbacks 是一個全域性的訊息頻道,在每次呼叫 nextTick的時候,去新增一個 function ,這個 function 裡面最主要做的就是一個判斷 cb 是否存在,存在就把 cb 的 this 指向 ctx,如果 _resolve 存在的話,就直接呼叫它,並把 ctx 傳給它。

if (!pending) { 
pending = true;
if (useMacroTask) {
macroTimerFunc();

} else {
microTimerFunc();

}
}複製程式碼

pending 是一個全域性的狀態,初始化是 false ,在第一次呼叫它的時候,直接改為 true ,並且要檢查當前執行的是巨集任務還是微任務,在執行到任務棧後,pending 變為 false,具體什麼是巨集任務,什麼是微任務,看這裡:js事件迴圈機制(event loop)

if (!cb &
&
typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;

})
}複製程式碼

這裡程式碼很簡單,這裡就不做太多解釋了,大家不懂了評論把。?


Vue.options = Object.create(null);
複製程式碼

給 Vue 的 options 新增一個原子。

ASSET_TYPES.forEach(function (type) { 
Vue.options[type + 's'] = Object.create(null);

});
複製程式碼

給 options 新增 n 個屬性,每個屬性都是一個原子:

var ASSET_TYPES = [  'component',  'directive',  'filter'];
複製程式碼

這是 ASSET_TYPES 所有的陣列項;

Vue.options._base = Vue;
複製程式碼

這用於標識“基礎”建構函式以擴充套件所有普通物件;

extend(Vue.options.components, builtInComponents);
複製程式碼

extend 在第一章已經講過了,在這裡:Vue 原始碼解析(例項化前) – 初始化全域性API(一)


使用外掛 initUse

initUse(Vue);
複製程式碼

這個方法,其實實現的就是一個 use 的方法,用來新增外掛的:

function initUse(Vue) { 
Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if (installedPlugins.indexOf(plugin) >
-1) {
return this
} var args = toArray(arguments, 1);
args.unshift(this);
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args);

} else if (typeof plugin === 'function') {
plugin.apply(null, args);

} installedPlugins.push(plugin);
return this
};

}複製程式碼

在這裡,就是給 Vue 建構函式,在例項化前繫結哪些外掛,所有的外掛,都在 installedPlugins 裡面。


初始化合並 initMixin$1

function initMixin$1(Vue) { 
Vue.mixin = function (mixin) {
this.options = mergeOptions(this.options, mixin);
return this
};

}複製程式碼

mergeOptions 方法的實現在這裡:Vue 原始碼解析(例項化前) – 初始化全域性API(一)


初始化繼承 initExtend

Vue.extend = function (extendOptions) { 
extendOptions = extendOptions || {
};
var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {
});
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
} var name = extendOptions.name || Super.options.name;
if (process.env.NODE_ENV !== 'production' &
&
name) {
validateComponentName(name);

} var Sub = function VueComponent (options) {
this._init(options);

};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions( Super.options, extendOptions );
Sub['super'] = Super;
// For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. if (Sub.options.props) {
initProps$1(Sub);

} if (Sub.options.computed) {
initComputed$1(Sub);

} // allow further extension/mixin/plugin usage Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type];

});
// enable recursive self-lookup if (name) {
Sub.options.components[name] = Sub;

} // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({
}, Sub.options);
// cache constructor cachedCtors[SuperId] = Sub;
return Sub
};

}複製程式碼

這裡,就是 extend 的方法的實現,其實最主要就是輸出了一個函式,繼承了 Vue 建構函式的所有屬性和方法

extendOptions = extendOptions || {
};
var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {
});
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}複製程式碼

檢查 extendOptions 是否存在,如果存在 extendOptions 上是否存在 _Ctor 並且存在 SuperId ,如果存在直接就返回 cachedCtors[SuperId];

var name = extendOptions.name || Super.options.name;
if (process.env.NODE_ENV !== 'production' &
&
name) {
validateComponentName(name);

}複製程式碼

這裡檢查的就是 name 的格式,是否是 xxx-xxx 這種格式的,在第一章裡面有詳細解釋;

if (Sub.options.props) { 
initProps$1(Sub);

}function initProps$1 (Comp) {
var props = Comp.options.props;
for (var key in props) {
proxy(Comp.prototype, "_props", key);

}
}複製程式碼

這裡做了初始化屬性的遍歷,給所有屬性都繫結了 Object.defineProperty;

var sharedPropertyDefinition = { 
enumerable: true, configurable: true, get: noop, set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;

};
Object.defineProperty(target, key, sharedPropertyDefinition);

}複製程式碼

這是 proxy 函式的實現過程。

if (Sub.options.computed) { 
initComputed$1(Sub);

}function initComputed$1 (Comp) {
var computed = Comp.options.computed;
for (var key in computed) {
defineComputed(Comp.prototype, key, computed[key]);

}
}function defineComputed ( target, key, userDef) {
var shouldCache = !isServerRendering();
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef;
sharedPropertyDefinition.set = noop;

} else {
sharedPropertyDefinition.get = userDef.get ? shouldCache &
&
userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop;
sharedPropertyDefinition.set = userDef.set ? userDef.set : noop;

} if (process.env.NODE_ENV !== 'production' &
&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn( ("Computed property \"" + key + "\" was assigned to but it has no setter."), this );

};

} Object.defineProperty(target, key, sharedPropertyDefinition);

}複製程式碼

這段程式碼是對 computed 做的處理,也是繫結了 Object.defineProperty,這種類似的程式碼處理條件,在前兩章已經講了很多了,大家這裡看一下其實就差不多了,如果不明白,就去看看前兩章把。

Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type];

});
if (name) {
Sub.options.components[name] = Sub;

}Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({
}, Sub.options);
// cache constructorcachedCtors[SuperId] = Sub;
return Sub複製程式碼

這裡其實就是讓繼承 Vue 的子級方法,可以做 Vue 可以做的事情。


初始化資源註冊 initAssetRegisters

function initAssetRegisters(Vue) { 
ASSET_TYPES.forEach(function (type) {
Vue[type] = function ( id, definition ) {
if (!definition) {
return this.options[type + 's'][id]
} else {
if (type === 'component') {
validateComponentName(id);

} if (type === 'component' &
&
isPlainObject(definition)) {
definition.name = definition.name || id;
definition = this.options._base.extend(definition);

} if (type === 'directive' &
&
typeof definition === 'function') {
definition = {
bind: definition, update: definition
};

} this.options[type + 's'][id] = definition;
return definition
}
};

});

}複製程式碼

這個方法,就是把 ASSET_TYPES 所有的陣列項當作 Vue 的方法名來定義,這裡就只有註冊,沒有做太多的事情,最後返回處理後的 definition。

結束語

到這裡,在例項化 Vue 前要做的事情,其實已經講解了70%左右了,下一章,我會把剩下所有初始化對 Vue 建構函式做處理的地方做逐一講解(我是看了一萬多行原始碼找的)。

哪裡不對,還希望大家多多指點,?

來源:https://juejin.im/post/5c3e8ba0f265da614a3aba23

相關文章