內功修鍊之lodash——Object系列
- 2019 年 12 月 2 日
- 筆記
如果覺得沒有面試題,那麼lodash每一個方法就可以當作一個題目,可以看著效果反過來實現,以不同的方法實現、多種方法實現,鞏固基礎。除了某些一瞬間就可以實現的函數,下面抽取部分函數作為試煉。時代在進步,下文所有的解法都採用es2015+
本文實現方法都是看效果倒推實現方法,並進行一些拓展和思考,和源碼無關。lodash這個庫在這裡更像一個題庫,給我們刷題的
能收穫什麼:
- 修鍊程式碼基本功,了解常見的套路
- 了解到一些操作的英文命名和規範
- 積累經驗,面對複雜邏輯問題可以迅速解決
- 也許可以查到自己的js基礎知識的漏洞
注意:
- 三星難度以上的會具體拓展和講解
- 文中使用的基本都是數組原生api以及es6+函數式編程,程式碼簡潔且過程清晰
- 如果說性能當然是命令式好,實現起來稍微麻煩一些而且比較枯燥無味
- 時代在進步,人生苦短,我選擇語法糖和api。面臨大數據的性能瓶頸,才是考慮命令式編程的時候
assignIn
_.assignIn(object, [sources])這個方法類似 Object.assign。 但是它會遍歷並繼承來源對象的屬性。注意: 這方法會改變源對象- 參數object (Object)是目標對象。[sources] (...Object)是來源對象
- 返回值 (Object)是assignIn後的返回對象
- 難度係數: ★★
- 建議最長用時:6min
// example function Foo() { this.b = 2; } function Bar() { this.d = 4; } Foo.prototype.c = 3; Bar.prototype.e = 5; _.assignIn({ 'a': 1 }, new Foo, new Bar); // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 } 複製程式碼參考程式碼:
function assignIn(target, ...newObjs) { return newObjs.reduce((res, o) => { for (const key in o) { res[key] = o[key]; } Object.getOwnPropertySymbols(o).forEach(sb => { res[sb] = o[sb]; }); return res; }, target); } 複製程式碼at
_.at(object, [paths])根據 object 的路徑獲取值為數組。object (Object)是要遍歷的對象[paths] (...(string|string[])是指要獲取的對象的元素路徑,單獨指定或者指定在數組中- 返回值是選中值的數組
- 難度係數: ★★★
- 建議最長用時:9min
//example var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; _.at(object, ['a[0].b.c', 'a[1]']); // => [3, 4] _.at(['a', 'b', 'c'], 0, 2); // => ['a', 'c'] 參考程式碼
// 這裡順便把get都實現了 function get(o, path) { // _和$都可以做key的哦 // `${path}`考慮到傳入的是數字 const keys = `${path}`.match(/(w|$)+/g) if (keys) { return keys.reduce((acc, key, i) => acc && acc[key] , o) } } function at(target, ...paths) { return paths.reduce((res, path) => { if (Array.isArray(path)) { return [...res, ...path.map(key => get(target, key))] } return [...res, get(target, path)] }, []) } 複製程式碼defaultsDeep
_.defaultsDeep(object, [sources])分配來源對象的可枚舉屬性到目標對象所有解析為 undefined 的屬性上。 來源對象從左到右應用。 一旦設置了相同屬性的值,後續的將被忽略掉。會遞歸分配默認屬性。注意: 這方法會改變源對象- 參數object (Object): 目標對象
[sources] (...Object): 來源對象- 返回值 (Object): 返回對象
- 難度係數: ★★★
- 建議最長用時:9min
// example _.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } }); // => { 'user': { 'name': 'barney', 'age': 36 } } 參考程式碼
function defaults(target, obj) { // 基本數據類型、null排除 if (!obj || typeof obj !== 'object') { return target; } return Object.entries(obj).reduce((acc, [key, value]) => { if (acc[key] === undefined) { acc[key] = value } else if (typeof acc[key] === 'object') { // 遞歸 defaults(acc[key], value) } return acc }, target) } function defaultsDeep(target, ...objs) { return objs.reduce((acc, obj) => { defaults(acc, obj) return acc; }, target) } 複製程式碼merge
_.merge(object, [sources])遞歸合併來源對象的自身和繼承的可枚舉屬性到目標對象。 跳過來源對象解析為 undefined 的屬性。 數組和普通對象會遞歸合併,其他對象和值會被直接分配。 來源對象從左到右分配,後續的來源對象屬性會覆蓋之前分配的屬性。注意: 這方法會改變源對象- 參數:
object (Object)目標對象。[sources] (...Object)是指來源對象 - 返回值 (Object):返回對象
- 難度係數: ★★★
- 建議最長用時:9min
// example var users = { 'data': [{ 'user': 'barney' }, { 'user': 'fred' }] }; var ages = { 'data': [{ 'age': 36 }, { 'age': 40 }] }; _. (users, ages); // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] } 參考程式碼
function mergeOne(target, obj) { return Object.entries(obj).reduce((acc, [key, value]) => { if (typeof acc[key] === 'object' && typeof value === 'object') { mergeOne(acc[key], value) } else { acc[key] = value } return acc }, target) } function merge(target, ...objs) { return objs.reduce((acc, obj) => { mergeOne(acc, obj) return acc }, target) } 複製程式碼set
_.set(object, path, value)設置值到對象對應的屬性路徑上,如果沒有則創建這部分路徑。 缺少的索引屬性會創建為數組,而缺少的屬性會創建為對象。 使用 _.setWith 訂製創建。- 參數
object (Object)是要修改的對象。path (Array|string)是要設置的對象路徑,value (*)是要設置的值 - 返回值 (Object)是返回設置的對象
- 難度係數: ★★★
- 建議最長用時:9min
// example var object = { 'a': [{ 'b': { 'c': 3 } }] }; _.set(object, 'a[0].b.c', 4); console.log(object.a[0].b.c); // => 4 _.set(object, 'x[0].y.z', 5); console.log(object.x[0].y.z); // => 5 參考程式碼
// 對於前面實現的get改造一下 function get(o, path, defaultValue) { const keys = `${path}`.match(/(w|$)+/g) if (keys) { return keys.reduce((acc, key) => acc ? (acc[key] === undefined ? (acc[key] = defaultValue) : acc[key]) : (acc[key] = defaultValue) , o) } } function set(target, path, value) { const paths = `${path}`.match(/(w|$)+/g) if (paths && paths.length) { const lastKey = paths.pop() const last = get(target, paths.join('.'), {}) last[lastKey] = value } return target } 複製程式碼unset
_.unset(object, path)移除對象路徑的屬性。 注意: 這個方法會改變源對象- 參數
object (Object)要修改的對象,path (Array|string)要移除的對象路徑 - 移除成功返回 true,否則返回 false
- 難度係數: ★★★
- 建議最長用時:9min
// example var object = { 'a': [{ 'b': { 'c': 7 } }] }; _.unset(object, 'a[0].b.c'); // => true console.log(object); // => { 'a': [{ 'b': {} }] }; _.unset(object, 'a[0].b.c'); // => true console.log(object); // => { 'a': [{ 'b': {} }] }; 參考程式碼
function unset(object, path) { const paths = `${path}`.match(/(w|$)+/g) if (paths && paths.length) { const lastKey = paths.pop() let temp = object while (paths.length) { temp = temp[paths.shift()] } if (lastKey in temp) { delete temp[lastKey] return true } return false } return false } 複製程式碼

