下面會用到undefined判斷,所以這裏先來跟underScore學習一下undefined的處理
在javaScript中我們判斷一個變量是否是undefined通常會這樣寫
var a;
if (a === undefined) {
console.log(1) // 1
}
但是在javaScript中undefined並不可靠,因爲undefined可以作爲變量名使用:
var a;
var undefined = 2
console.log(undefined) // 2
if (a === undefined) {
console.log(1)
}
underScore中通過這樣的方法來獲取undefined:
console.log(void 0) // undefined
console.log(void (0)) //undefined
好了,切入正題:
就像數組原型上又map方法,允許對數組處理並返回,underScore中也有map方法,並且比Array.prototype.map更加健全,不能叫健全吧,應該說是健壯。下面從map方法的使用上來分析源碼。
var arr = [4, 6, 1]
_.map(arr, function(item, index) {
console.log(item, index)
})
如果我不傳遞迭代器呢?會將原對象返回:
var arr = [4, 6, 1]
var a = _.map(arr)
console.log(a)
如果我傳遞一個一個對象呢?
var arr = [4, 6, 1]
var obj = [{ name: '麥樂' }, { name: 'maile' }]
var a = _.map(obj, { name: 'maile' })
console.log(a)
也支持傳遞字符串
var arr = [4, 6, 1]
var obj = [{ name: '麥樂' }, { name: 'maile' }]
var a = _.map(obj, 'name')
console.log(a)
可以分析出,map可以根據傳遞的參數不同,返回不同的值,具體分以下幾種情況:
- 當迭代器也就是iteratee不傳遞時,返回第一個參數
- 當iteratee傳遞爲函數時,正常處理
- 當iteratee傳遞爲對象時,返回匹配結果
-
iteratee傳遞爲字符串時,返回匹配到的鍵值數組
看下源碼是怎麼實現的?
// Return the results of applying the iteratee to each element.
function map(obj, iteratee, context) {
iteratee = cb(iteratee, context);
var _keys = !isArrayLike(obj) && keys(obj),
length = (_keys || obj).length,
results = Array(length);
for (var index = 0; index < length; index++) {
var currentKey = _keys ? _keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
return results;
}
函數接受三個參數,對象,迭代器,上下文對象也就是this。
這裏將我們傳遞進去的迭代器做了處理,就是上面出現的幾種情況,cb函數是underScore內部的一個函數,就是負責處理不同的迭代器參數的。源碼內部多處用到了這個函數。
iteratee = cb(iteratee, context);
cb
function cb(value, context, argCount) {
if (_.iteratee !== iteratee) return _.iteratee(value, context);
return baseIteratee(value, context, argCount);
}
_.iteratee
_.iteratee = iteratee;
function iteratee(value, context) {
return baseIteratee(value, context, Infinity);
}
可以看到一般情況下_.iteratee === iteratee 是true,_.iteratee(value, context);不會執行,但是當我們在外部自定義一個這樣的函數,迭代器函數被修改這時返回的下面這個函數的調用結果,也就是說允許我們修改map函數的功能。
_.iteratee = function(value, context) {
}
比如,如果不想考慮那麼多,只允許第二個參數是函數,就可以這麼寫
var arr = [4, 6, 1]
_.iteratee = function(value, context) {
if (typeof value !== 'function') throw Error('第二個參數必須是函數')
return function(...agrs) {
value.call(context, agrs)
}
};
_.map(arr, 'aa')
baseIteratee
大多數情況下,不會去修改iteratee,cb函數一般都會走到下面這一步:
return baseIteratee(value, context, argCount);
這裏調用了baseIteratee函數,源碼中這個函數如下:
function baseIteratee(value, context, argCount) {
if (value == null) return identity;
if (isFunction(value)) return optimizeCb(value, context, argCount);
if (isObject(value) && !isArray(value)) return matcher(value);
return property(value);
}
可以看到,實在這個函數中對傳遞進來的第二個參數做了處理:
identity
if (value == null) return identity;
function identity(value) {
return value;
}
初始化了一個迭代器,返回傳遞進來的參數。
optimizeCb
if (isFunction(value)) return optimizeCb(value, context, argCount);
如果是函數,就交給內部的optimizeCb函數去處理:
function optimizeCb(func, context, argCount) {
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1: return function(value) {
return func.call(context, value);
};
// The 2-argument case is omitted because we’re not using it.
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments);
};
}
大多數情況下,都不會傳遞context,直接返回了func,也就是傳遞進來的迭代器。如果傳遞了context,只需這樣返回就可以滿足需求:
var optimizeCb = function(func, context) {
if (context === void 0) return func;
return function() {
return func.apply(context, arguments);
};
};
但是 underScore中卻對傳遞的參數個數進行分類處理。之所以這麼做就是爲了避免使用arguments。
比如map中,迭代器的調用就傳遞了三個參數:
results[index] = iteratee(obj[currentKey], currentKey, obj);
這裏沒有傳遞argCount,optimizeCb中,argCount默認是3。返回
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
正好對應iteratee(obj[currentKey], currentKey, obj);這裏傳遞的3個參數。
matcher
if (isObject(value) && !isArray(value)) return matcher(value);
第二個參數是對象,並且不是數組,也就是這樣的情況:
var arr = [4, 6, 1]
var obj = [{ name: '麥樂' }, { name: 'maile' }]
var a = _.map(obj, { name: 'maile' })
console.log(a) // [false, true]