如果覺得沒有面試題,那麼lodash每一個方法就可以當作一個題目,可以看著效果反過來實現,以不同的方法實現、多種方法實現,鞏固基礎。除了某些一瞬間就可以實現的函式,下面抽取部分函式作為試煉。時代在進步,下文所有的解法都採用es2015+
本文實現方法都是看效果倒推實現方法,並進行一些擴充和思考,和原始碼無關。lodash這個庫在這裡更像一個題庫,給我們刷題的
能收穫什麼:
- 修煉程式碼基本功,瞭解常見的套路
- 瞭解到一些操作的英文命名和規範
- 積累經驗,面對複雜邏輯問題可以迅速解決
- 也許可以查到自己的js基礎知識的漏洞
概念:
- SameValue標準: 目前已有等價的api——Object.is(a, b),表示a和b在SameValue標準下是否相等。Object.is和===不同的地方在於,可以判斷NaN和NaN相等,但是0 和 -0是不相等
- SameValueZero標準: 與SameValue差別僅僅在於,此標準下0和-0是相等的,Array.prototype.includes、Set.prototype.has內部就是使用SameValueZero
注意:
- 三星難度以上的會具體擴充和講解
- 文中使用的基本都是陣列原生api以及es6+函數語言程式設計,程式碼簡潔且過程清晰
- 如果說效能當然是命令式好,實現起來稍微麻煩一些而且比較枯燥無味
- 時代在進步,人生苦短,我選擇語法糖和api。面臨大資料的效能瓶頸,才是考慮指令式程式設計的時候
chunk
- 描述:
_.chunk(array, [size=0])
,將陣列拆分成多個 size 長度的塊,並組成一個新陣列。 如果陣列無法被分割成全部等長的塊,那麼最後剩餘的元素將組成一個塊。 - 難度係數: ★★
- 建議最長用時:6min
// example
_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]
_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]
複製程式碼
參考程式碼
// 常規方法
function chunk(arr, size = 0) {
let current = 0;
let temp = [];
return arr.reduce((acc, cur, i) => {
if (current++ < size) {
temp.push(cur);
}
if (temp.length === size || i === arr.length - 1) {
acc.push(temp);
current = 0;
temp = [];
}
return acc;
}, []);
}
// 抽象派
function chunk(arr, size = 0) {
let current = 0;
let temp = [];
return arr.reduce(
(acc, cur, i) =>
temp.push(...(current++ < size ? [cur] : [])) === size ||
i === arr.length - 1
? [...acc, temp, ...((current = 0), (temp = []), [])]
: acc,
[]
);
}
複製程式碼
zip系列
zip&unzip
_.zip([arrays])
建立一個打包所有元素後的陣列。第一個元素包含所有提供陣列的第一個元素,第二個包含所有提供陣列的第二個元素,以此類推。- 引數[arrays] (...Array),表示要處理的陣列佇列
- 返回值 (Array)是一個打包後的陣列
_.unzip(array)
類似_.zip
,接收一個打包後的陣列並且還原為打包前的狀態。 - 難度係數: ★
- 建議最長用時:2min * 2 = 4min
zip&unzip的例子
var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
// => [['fred', 30, true], ['barney', 40, false]]
_.unzip(zipped);
// => [['fred', 'barney'], [30, 40], [true, false]]
複製程式碼
參考程式碼
function zip(target, ...arrs) {
return target.map((item, index) => [item, ...arrs.map(arr => arr[index])])
}
function unzip(arrs) {
return arrs.reduce((acc, arr) => {
arr.forEach((item, index) => {
acc[index] = acc[index] || [];
acc[index].push(item)
})
return acc
}, [])
}
複製程式碼
zipWith & unzipWith
_.zipWith
類似_.zip
, 它另外接受一個 iteratee 決定如何重組值。 iteratee 會呼叫每一組元素,最後返回一個打包後的陣列_.unzipWith(array, [iteratee=_.identity])
另外接受一個 iteratee 來決定如何重組解包後的陣列。iteratee 會傳入4個引數:(accumulator, value, index, group)。每組的第一個元素作為初始化的值,返回一個解包後的陣列- 難度係數: ★★
- 建議最長用時:6min
// example
_.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
return a + b + c;
});
// => [111, 222]
// unzipWith
var zipped = _.zip([1, 2], [10, 20], [100, 200]);
// => [[1, 10, 100], [2, 20, 200]]
_.unzipWith(zipped, _.add);
// => [3, 30, 300]
複製程式碼
參考程式碼
// zipWith稍微修改一下就可以實現
function zipWith(target, ...arrs) {
const iteratee = arrs.pop()
return target.map((item, index) => [item, ...arrs.map(arr => arr[index])]).map(group => iteratee(...group))
}
function unzipWith(arrs, iteratee) {
// 使用唯一標記,避免`!result`的假值誤判
const FIRST_FLAG = Symbol()
return arrs.reduce((acc, arr) => {
arr.forEach((item, index) => {
acc[index] = acc[index] || []
acc[index].push(item)
})
return acc
}, []).map(group => group.reduce((result, cur, index, all) =>
result === FIRST_FLAG ? cur : iteratee(result, cur, index, all)
), FIRST_FLAG)
}
複製程式碼
zipObject & zipObjectDeep
_.zipObject([props=[]], [values=[]])
,接受屬性key的陣列和values的陣列,返回每一對k-v形成的物件- 難度係數: ★
- 建議最長用時:2min
//example
_.zipObject(['a', 'b'], [1, 2]);
// => { 'a': 1, 'b': 2 }
複製程式碼
function zipObject(keys, values) {
return keys.reduce((obj, key, index) => {
obj[key] = values[index]
return obj
}, {})
}
複製程式碼
_.zipObjectDeep([props=[]], [values=[]])
,類似 _.zipObject,它還支援屬性路徑。- 難度係數: ★★★★
- 建議最長用時:12min
// example
_.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
// => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
複製程式碼
參考程式碼
// 從 'a'或者'[1]'這種摳出真正的key: a、1
function getRealKey(currentKey) {
return /\[(\d+)\]/.test(currentKey) ? RegExp.$1 : currentKey
}
function zipObjectDeep(keys, values) {
return keys.reduce((obj, key, index) => {
const path = key.split(/\.|\B(?=\[\d+\])|\b(?=\[\d+\])/)
// 輔助變數temp,利用引用型別的特性遍歷和賦值整個物件
let temp = obj
// 預留一個空位,最後一下賦值
while(path.length > 1) {
const currentKey = path.shift()
const realKey = getRealKey(currentKey)
// 如果下一個是[1]這種,那麼就是陣列,不然就是一個物件
// 如果你想給下一層的屬性賦值,那麼就要提前準備好它上一層的結構
temp[realKey] = temp[realKey] || (/\[(\d+)\]/.test(path[0]) ? [] : {})
temp = temp[realKey]
}
// 最後一下就是賦值了
const lastKey = getRealKey(path.shift())
temp[lastKey] = values[index]
return obj
}, {})
}
複製程式碼
關於正則key.split(/\.|\B(?=\[\d+\])|\b(?=\[\d+\])/)
的分析:
- split是可以傳入正則的哦,對匹配到的內容進行分割。除了普通的
.
,還要考慮類似[0]
這種,這種需要匹配到邊界才可以完美分割 - 分成3部分,
.
、單詞邊界+[數字]、非單詞邊界+[數字] .
匹配到的split一下就是'a.b.c' => ['a', 'b', 'c']
- 單詞邊界+[數字]
'a[1]' => ['a', '[1]']
- 非單詞邊界+[數字]
'[0][1]' => ['[0]', '[1]']
- ?=是正向0寬斷言,也就是說
/a(?=xx)/
匹配前面是xx
的字元a
,且xx
不納入捕獲組中
groupBy
_.groupBy(collection, [iteratee=_.identity])
key 是經 iteratee 處理的結果, value 是產生 key 的元素陣列。 iteratee 會傳入1個引數:(value)。- 引數:
collection (Array|Object)
是需要遍歷的集合。[iteratee=_.identity] (Function|Object|string)
是一個函式,這個函式會處理每一個元素(和其他By系列的方法都是一樣的,傳入函式和With一樣的效果,傳入字串或者陣列會先呼叫_.property
) - 返回一個組成彙總的物件
- 難度係數: ★★(如果不知道property方法實現,再加多兩星難度)
- 建議最長用時:6min
// example
_.groupBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': [4.2], '6': [6.1, 6.3] }
// 使用了 `_.property` 的回撥結果
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }
複製程式碼
參考程式碼
function property(path) {
return function (o) {
const temp = Array.isArray(path) ? path : path.split(".");
let res = o;
while (temp.length && res) {
res = res[temp.shift()];
}
return res;
};
}
function groupBy(...arrs) {
let iteratee = arrs.pop();
iteratee = typeof iteratee === 'function' ? iteratee : property(iteratee);
return arrs.reduce((acc, arr) => {
arr.forEach(item => {
const key = iteratee(item)
;(acc[key] || (acc[key] = [])).push(item)
})
return acc
}, {})
}
複製程式碼
invokeMap
_.invokeMap(collection, path, [args])
呼叫 path 的方法處理集合中的每一個元素,返回處理的陣列。 如果方法名是個函式,集合中的每個元素都會被呼叫到。- 引數:
collection (Array|Object)
是需要遍歷的集合,path (Array|Function|string)
是要呼叫的方法名 或者 這個函式會處理每一個元素。[args] (...*)
給方法傳入的引數 - 返回陣列結果
- 難度係數: ★
- 建議最長用時:3min
// example
_.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
// => [[1, 5, 7], [1, 2, 3]]
_.invokeMap([123, 456], String.prototype.split, '');
// => [['1', '2', '3'], ['4', '5', '6']]
複製程式碼
參考程式碼
function invokeMap(arr, fn, ...args) {
return arr.map(item => {
// 面對這種傳入函式手動呼叫的,都記得call/apply一下
return (typeof fn === 'function' ? fn : arr[fn]).apply(item, args)
})
}
複製程式碼
lodash的陣列和collection的方法就此告一段落了,其他方法基本都是不需要1分鐘就可以寫出來或者沒有什麼坑點的。後面是function系列,to be continue
關注公眾號《不一樣的前端》,以不一樣的視角學習前端,快速成長,一起把玩最新的技術、探索各種黑科技