以前,咱們在網上,能夠看到不少有關vue部分功能的實現原理,尤爲是數據雙向綁定那一塊的,文章不少,可是都是按照一樣的思想去實現的一個數據雙向綁定的功能,但不是vue的源碼。javascript
今天,我在一行一行的去看vue的全部代碼,並挨個做出解釋,這個時候咱們能夠發現,vue的細節,很值得咱們去學習。vue
你們以爲寫的有用的話,幫忙點個關注,點點贊,有問題能夠評論,只要我看到,我會第一時間回覆。java
話很少說,直接開始了。ios
initGlobalAPI(Vue);
複製代碼
這個時候,初始化調用initGlobalAPI,傳入Vue構造函數。這裏是在Vue構造函數實例化以前要作的事情,因此這裏先不講Vue對象裏面作了什麼,先講實例化以前作了什麼。編程
function initGlobalAPI (Vue) {
// config
var configDef = {};
configDef.get = function () { return config; };
if (process.env.NODE_ENV !== 'production') {
configDef.set = function () {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
);
};
}
Object.defineProperty(Vue, 'config', configDef);
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
warn: warn,
extend: extend,
mergeOptions: mergeOptions,
defineReactive: defineReactive
};
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);
});
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents);
initUse(Vue);
initMixin$1(Vue);
initExtend(Vue);
initAssetRegisters(Vue);
}
複製代碼
這是initGlobalAPI方法的全部代碼,行數很少,可是知識點不少。api
var configDef = {};
複製代碼
這個函數聲明瞭一個configDef得空對象;數組
configDef.get = function () { return config; };
複製代碼
而後在給configDef添加了一個get屬性,這個屬性返回得是一個config對象,這個cofig對象裏面,有n個屬性,下面來一一解釋一下:less
var config = ({
optionMergeStrategies: Object.create(null),
silent: false,
productionTip: process.env.NODE_ENV !== 'production',
devtools: process.env.NODE_ENV !== 'production',
performance: false,
errorHandler: null,
warnHandler: null,
ignoredElements: [],
keyCodes: Object.create(null),
isReservedTag: no,
isReservedAttr: no,
isUnknownElement: no,
getTagNamespace: noop,
parsePlatformTagName: identity,
mustUseProp: no,
_lifecycleHooks: LIFECYCLE_HOOKS
})
複製代碼
optionMergeStrategies:選項合併,用於合併core / util / optionside
默認值:object.creart(null)函數式編程
注:object.creart(null)去建立的一個是原子,什麼是原子呢,就是它是對象,可是不繼承Object() ,這裏對原子的概念不作深究,你們若是感興趣,能夠百度去查「js元系統」,aimingoo對這方面有作過詳細的說明。
silent:是否取消警告
默認值:false
productionTip:項目啓動時,是否顯示提示信息
默認值:process.env.NODE_ENV !== 'production'
若是是開發環境,則是true,表示顯示提示信息,在生產環境則不顯示
devtools:是否啓用devtools
默認值:同productionTip
performance:是否記錄性能
默認值:false
errorHandler:觀察程序錯誤的錯誤處理程序
默認值:null
warnHandler:觀察程序警告的警告處理程序
默認值:null
ignoredElements:忽略某些自定義元素
默認值:[]
keyCodes:v - on的自定義用戶keyCode
默認值:object.creart(null)
isReservedTag:檢查是否保留了標記,以便它不能註冊爲組件。這取決於平臺,可能會被覆蓋
var no = function (a, b, c) { return false; };
複製代碼
默認值:一個名爲no的function,這個function接收三個參數,可是結果永遠返回的是false
isReservedAttr:檢查屬性是否被保留,以便不能用做組件道具。這取決於平臺,可能會被覆蓋
默認值:同上
isUnknownElement:檢查標記是否爲未知元素。取決於平臺
默認值:同上
getTagNamespace:獲取元素的命名空間
function noop (a, b, c) {}
複製代碼
默認值:一個名爲noop的函數,裏面什麼都沒有作
parsePlatformTagName:解析特定平臺的真實標籤名稱
var identity = function (_) { return _; };
複製代碼
默認值:一個名爲identity的函數,輸入的什麼就輸出的什麼
mustUseProp:檢查是否必須使用屬性(例如值)綁定屬性。這個取決於平臺
默認值:一個名爲no的function
_lifecycleHooks:生命週期鉤子數組
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured'
];
複製代碼
默認值:一個數組,裏面有全部生命週期的方法名
以上就是config裏面全部的屬性
if (process.env.NODE_ENV !== 'production') {
configDef.set = function () {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
);
};
}
複製代碼
作了一個判斷是不是生產環境,若是不是生產環境,給configDef添加一個set方法
Object.defineProperty(Vue, 'config', configDef);
複製代碼
在這裏,爲Vue的構造函數,添加一個要經過Object.defineProperty監聽的屬性config,獲取的時候,獲取到的是上面描述的那個config對象,若是對這個config對象直接作變動,就會提示「不要替換vue.config對象,而是設置單個字段」,說明,做者不但願咱們直接去替換和變動整個config對象,若是有須要,但願去直接修改咱們須要修改的值
Vue.util = {
warn: warn,
extend: extend,
mergeOptions: mergeOptions,
defineReactive: defineReactive
};
複製代碼
在這裏,設置了一個公開的util對象,可是它不是公共的api,避免依賴,除非你意識到了風險,下面來介紹一下它的屬性:
var warn = noop;
var generateComponentTrace = (noop);
if (process.env.NODE_ENV !== 'production') {
warn = function (msg, vm) {
var trace = vm ? generateComponentTrace(vm) : '';
if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace);
} else if (hasConsole && (!config.silent)) {
console.error(("[Vue warn]: " + msg + trace));
}
};
}
複製代碼
warn是一個function,初始化的時候,只定義了一個noop方法,若是在開發環境,這個warn是能夠接收兩個參數,一個是msg,一個是vm,msg不用說,你們都知道這裏是一個提示信息,vm就是實例化的vue對象,或者是實例化的vue對象的某一個屬性。
接下來是一個三元表達式trace,用來判斷調用warn方法時,是否有傳入了vm,若是沒有,返回的是空,若是有,那麼就返回generateComponentTrace這個function,這個方法初始化的值也是noop,什麼都沒有作,目的是解決流量檢查問題
若是config.warnHandler被使用者變動成了值,不在是null,那麼就把config.warnHandler的this指向null,其實就是指向的window,再把msg, vm, trace傳給config.warnHandler
不然判斷當前環境使用支持conosle而且開啓了警告,若是開啓了,那就把警告提示信息打印出來
function extend (to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to
}
複製代碼
這個方法是用於作繼承操做的,接收兩個值to, _from,將屬性_from混合到目標對象to中,若是to存在_from中的屬性,則直接覆蓋,最後返回新的to
function mergeOptions (parent, child, vm) {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child);
}
if (typeof child === 'function') {
child = child.options;
}
normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirecitives(child);
var extendsFrom = child.extends;
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm);
}
if (child.mixins) {
for (var i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
var options = {};
var key;
for (key in parent) {
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
function mergeField (key) {
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
return options
}
複製代碼
if (process.env.NODE_ENV !== 'production') {
checkComponents(child);
}
function checkComponents (options) {
for (var key in options.components) {
validateComponentName(key);
}
}
function validateComponentName (name) {
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
);
}
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
);
}
}
複製代碼
這個方法接收三個參數parent,child,vm,在不是生產環境的狀況下,會去檢測參數child中,是否存在components,若是存在該對象,遍歷全部的componets,進行名稱是否符合規範,這裏有一個正則,是用來判斷以字母開頭,以0個或多個任意字母和字符「-」結尾的字符串,若是不符合這個規定的話,就會提示警告信息
if (typeof child === 'function') {
child = child.options;
}
複製代碼
若是child是一個function的話,則把child本身指向child的options屬性
接下來要作的就是規範child裏面的Props、Inject、Direcitives
normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirecitives(child);
複製代碼
function normalizeProps (options, vm) {
var props = options.props;
if (!props) { return }
var res = {};
var i, val, name;
if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
res[name] = { type: null };
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
}
} else if (isPlainObject(props)) {
for (var key in props) {
val = props[key];
name = camelize(key);
res[name] = isPlainObject(val)
? val
: { type: val };
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
"Invalid value for option \"props\": expected an Array or an Object, " +
"but got " + (toRawType(props)) + ".",
vm
);
}
options.props = res;
}
複製代碼
var props = options.props;
if (!props) { return }
複製代碼
一開始,會檢查child是否存在props屬性,若是不存在,直接return出去,若是存在的話則是去聲明瞭幾個變量,一個名爲res的對象,還有i, val, name
if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
res[name] = { type: null };
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
}
}
複製代碼
檢查props是數組仍是對象,若是是數組的話,則是去循環它,並判斷每個數組項,是不是字符串,若是是字符串那麼就去執行camelize方法。
camelize:
var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
});
複製代碼
把名稱格式爲「xx-xx」的變爲「xxXx」,這裏接收的是當前的props屬性值,一個字符串
cached:
function cached (fn) {
var cache = Object.create(null);
return (function cachedFn (str) {
var hit = cache[str];
return hit || (cache[str] = fn(str))
})
}
複製代碼
在調用camelize方法的時候,camelize調用了cached,這是一個暫存式函數,對暫存式函數不瞭解的朋友,能夠去看看函數式編程,在cached也是建立了一個原子cache,而後會返回一個cachedFn方法,這裏會檢測cache是否存在當前props屬性值的屬性,若是存在,直接返回,若是不存在,則是調用,調用cached的方法傳過來的function,在調用cached方法的方法中返回的結果,返回到調用cached方法的方法(這句話我知道很繞口,可是我只會這麼解釋,哪位大佬有更好的表述方式,歡迎評論,我作修改)
而後把全部的數組項,而且是字符串的,所有都遍歷一遍,作這樣的處理,而後在res對象裏面,去添加一個屬性,它是一個對象,屬性名就是遍歷後的這個遍歷後的值(把-轉換成大寫字母),屬性值有一個初始化的type屬性,值爲null
固然不是生產環境下,而且props雖然是數組,可是數組項不是字符串的話,會警告你「使用數組語法時,props必須是字符串」
var _toString = Object.prototype.toString;
function isPlainObject (obj) {
return _toString.call(obj) === '[object Object]'
}
else if (isPlainObject(props)) {
for (var key in props) {
val = props[key];
name = camelize(key);
res[name] = isPlainObject(val)
? val
: { type: val };
}
}
options.props = res;
複製代碼
若是child的props不是數組,使用isPlainObject去判斷props是不是對象,這個方法代碼就一行,很簡單,也比較好理解,我也就不浪費篇幅去解釋了;
若是是對象的話,就去遍歷它,把全部的屬性名按照上面數組項的處理方式,去處理全部的數組名,而且看成res的屬性名,該屬性名的值須要去判斷原props的該屬性的值是不是對象,若是是對象,直接看成當前屬性名的屬性值,若是不是的話,則給當前處理後的屬性名,傳一個對象,type屬性的值就是原props該屬性名的屬性值
這裏,就把child裏面全部的props給規範化了,最後覆蓋了源child的props屬性(這一個方法的內容真多,各類知識點,有沒有,點波贊吧)
function normalizeInject (options, vm) {
var inject = options.inject;
if (!inject) { return }
var normalized = options.inject = {};
if (Array.isArray(inject)) {
for (var i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] };
}
} else if (isPlainObject(inject)) {
for (var key in inject) {
var val = inject[key];
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val };
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
"Invalid value for option \"inject\": expected an Array or an Object, " +
"but got " + (toRawType(inject)) + ".",
vm
);
}
}
複製代碼
和props同樣,先檢查是否存在,不存在直接返回;
若是存在的話,把child的inject存在一個變量inject裏,把child裏面的inject變成空對象,而且把該值傳給一個normalized的變量;
若是inject是一個數組的話,則遍歷它,normalized的每個屬性名,就是每個inject的數組項,每個屬性值都是一個對象,對象的屬性from的值,就是每個inject的數組項
若是inject是一個對象的話,則遍歷它,把每個屬性值存爲變量val,normalized的key,就是inject的key,若是val是一個對象的話,則把{ from: key }和val合併,val覆蓋{ from: key }
function normalizeDirectives (options) {
var dirs = options.directives;
if (dirs) {
for (var key in dirs) {
var def = dirs[key];
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def };
}
}
}
}
複製代碼
源碼裏只處理了child.directives的對象格式,若是存在的話遍歷它,若是每個屬性值def都是function的話則把每個directives的屬性值改成{ bind: def, update: def };
到這裏,規範化的事情就作完了,休息一下,點個關注點個贊,我們繼續。
var extendsFrom = child.extends;
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm);
}
複製代碼
看child是否存在extends,遞歸當前的mergeOptions方法,parent就是當前的parent,child就是當前child的extends的值;
if (child.mixins) {
for (var i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
複製代碼
檢測child是否存在mixins,若是存在的話,遞歸當前的mergeOptions方法,並把最新的結果,去覆蓋上一次調用mergeOptions方法的parent;
var defaultStrat = function (parentVal, childVal) {
return childVal === undefined
? parentVal
: childVal
};
var strats = config.optionMergeStrategies;//這只是初始化的值
var options = {};
var key;
for (key in parent) {
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
function mergeField (key) {
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
return options
複製代碼
如今聲明瞭一個options的對象,而後分別去遍歷了parent和child,parent和child的key傳給了一個mergeField的方法;
在mergeField中聲明一個start變量,若是strats下的存在當前這個key的屬性,則返回,不然就返回一個默認的defaultStrat;
defaultStrat接收兩個參數,第一個參數是parent,第二個是child,若是child存在就返回child,不然就返回parent;
把mergeField接收到的key,看成以前optins的key,它的值就是前面返回的變量start方法返回的值;
最後,把整個options返回。
到這裏,Vue.util的四個屬性已經講了三個了,第四個屬性是一個defineReactive方法,我不打算在這一篇去講,由於這個方法,就是實現一個數據雙向綁定的核心方法,內容可能會比較多,並且這一篇的內容也已經夠長了,寫的再多的話,不適合學習了,因此我打算在下一篇單獨去講一下defineReactive這個方法。
這篇文章,是vue源碼解析的起始篇,接下來我會持續更新該系列的文章,歡迎你們批評和點評,仍是老話,多點關注,多點贊😊
謝謝你們。