JavaScript 開發人員的 10 個重要 Lodash 函數
已發表: 2021-10-05對於 JavaScript 開發人員,Lodash 無需介紹。 然而,圖書館很大,常常讓人感到不知所措。 不再!
洛達什,洛達什,洛達什。 . . 我從哪裡開始!
曾經有一段時間 JavaScript 生態系統還處於萌芽狀態。 如果願意的話,它可以與狂野的西部或叢林進行比較,那裡發生了很多事情,但是對於日常開發人員的挫折和生產力,幾乎沒有答案。
然後洛達什進入了現場,感覺就像洪水淹沒了一切。 從簡單的日常需求(如排序)到復雜的數據結構轉換,Lodash 加載了(甚至超載!)功能,將 JS 開發人員的生活變成了純粹的幸福。

Lodash今天在哪裡? 好吧,它仍然擁有它最初提供的所有好處,然後是一些,但它似乎已經失去了在 JavaScript 社區中的份額。 為什麼? 我能想到幾個原因:
- Lodash 庫中的一些函數在應用於大型列表時(並且仍然)很慢。 雖然這永遠不會影響 95% 的項目,但剩餘 5% 的有影響力的開發人員給了 Lodash 一個壞消息,並且影響層層遞進到基層。
- 在 JS 生態系統中有一種趨勢(甚至可能對 Golang 人說同樣的話),狂妄自大比必要的更為普遍。 所以,依賴像 Lodash 這樣的東西被認為是愚蠢的,當人們提出這樣的解決方案時,它會在 StackOverflow 等論壇上遭到抨擊(“什麼?!使用整個庫來做這樣的事情?我可以結合
filter()和reduce()來實現在一個簡單的函數中做同樣的事情!”)。 - 洛達什老了。 至少按照 JS 標準。 它於 2012 年問世,截至撰寫本文時,已經快十年了。 API一直很穩定,每年都沒有太多令人興奮的東西(只是因為沒有必要),這讓普通的過度興奮的JS開發人員感到無聊。
在我看來,不使用 Lodash 是我們 JavaScript 代碼庫的重大損失。 它已經證明了我們在工作中遇到的日常問題的無錯誤和優雅的解決方案,並且使用它只會使我們的代碼更具可讀性和可維護性。
話雖如此,讓我們深入了解一些常見的(或不常見的!)Lodash 函數,看看這個庫是多麼的有用和美麗。
克隆。 . . 深!
由於 JavaScript 中的對像是通過引用傳遞的,因此當希望克隆某些內容並希望新數據集不同時,這會讓開發人員頭疼。
let people = [ { name: 'Arnold', specialization: 'C++', }, { name: 'Phil', specialization: 'Python', }, { name: 'Percy', specialization: 'JS', }, ]; // Find people writing in C++ let folksDoingCpp = people.filter((person) => person.specialization == 'C++'); // Convert them to JS! for (person of folksDoingCpp) { person.specialization = 'JS'; } console.log(folksDoingCpp); // [ { name: 'Arnold', specialization: 'JS' } ] console.log(people); /* [ { name: 'Arnold', specialization: 'JS' }, { name: 'Phil', specialization: 'Python' }, { name: 'Percy', specialization: 'JS' } ] */ 請注意,在我們純真無邪的情況下,儘管我們是善意的,原始的people數組在此過程中發生了變異(Arnold 的專業化從C++變為JS )——對底層軟件系統的完整性造成重大打擊! 事實上,我們需要一種方法來製作原始數組的真實(深度)副本。

你也許會爭辯說這是一種“愚蠢”的 JS 編碼方式; 然而,實際情況有點複雜。 是的,我們有可愛的解構運算符可用,但是任何嘗試解構複雜對象和數組的人都知道這種痛苦。 然後,有使用序列化和反序列化(可能是 JSON)來實現深度複製的想法,但這只會使您的代碼對讀者來說更加混亂。
相比之下,當 Lodash 被使用時,看看這個解決方案是多麼優雅和簡潔:
const _ = require('lodash'); let people = [ { name: 'Arnold', specialization: 'C++', }, { name: 'Phil', specialization: 'Python', }, { name: 'Percy', specialization: 'JS', }, ]; let peopleCopy = _.cloneDeep(people); // Find people writing in C++ let folksDoingCpp = peopleCopy.filter( (person) => person.specialization == 'C++' ); // Convert them to JS! for (person of folksDoingCpp) { person.specialization = 'JS'; } console.log(folksDoingCpp); // [ { name: 'Arnold', specialization: 'JS' } ] console.log(people); /* [ { name: 'Arnold', specialization: 'C++' }, { name: 'Phil', specialization: 'Python' }, { name: 'Percy', specialization: 'JS' } ] */ 注意people數組在深度克隆後是如何保持不變的(在這種情況下,Arnold 仍然專注於C++ )。 但更重要的是,代碼簡單易懂。
從數組中刪除重複項
從數組中刪除重複項聽起來像是一個很好的面試/白板問題(請記住,如有疑問,請在問題上使用哈希圖!)。 而且,當然,您始終可以編寫自定義函數來執行此操作,但是如果遇到幾種不同的場景來使您的數組獨一無二,該怎麼辦? 您可以為此編寫其他幾個函數(並冒著遇到細微錯誤的風險),或者您可以只使用 Lodash!

我們的第一個獨特數組示例相當簡單,但它仍然代表了 Lodash 帶來的速度和可靠性。 想像一下通過自己編寫所有自定義邏輯來做到這一點!
const _ = require('lodash'); const userIds = [12, 13, 14, 12, 5, 34, 11, 12]; const uniqueUserIds = _.uniq(userIds); console.log(uniqueUserIds); // [ 12, 13, 14, 5, 34, 11 ]請注意,最終的數組未排序,當然,這在這裡無關緊要。 但是現在,讓我們想像一個更複雜的場景:我們從某個地方拉取了一組用戶,但我們希望確保它只包含唯一用戶。 輕鬆使用 Lodash!
const _ = require('lodash'); const users = [ { id: 10, name: 'Phil', age: 32 }, { id: 8, name: 'Jason', age: 44 }, { id: 11, name: 'Rye', age: 28 }, { id: 10, name: 'Phil', age: 32 }, ]; const uniqueUsers = _.uniqBy(users, 'id'); console.log(uniqueUsers); /* [ { id: 10, name: 'Phil', age: 32 }, { id: 8, name: 'Jason', age: 44 }, { id: 11, name: 'Rye', age: 28 } ] */ 在這個例子中,我們使用uniqBy()方法告訴 Lodash 我們希望對像在id屬性上是唯一的。 在一行中,我們表達了可能需要 10-20 行的內容,並引入了更多的錯誤範圍!
關於在 Lodash 中使事物變得獨特,還有更多可用的東西,我鼓勵您查看文檔。
兩個數組的差
聯合、差異等,聽起來像是最好留在高中關於集合論的枯燥講座中的術語,但它們在日常實踐中經常出現。 擁有一個列表並希望將另一個列表與其合併或想要找到與另一個列表相比哪些元素是唯一的,這是很常見的; 對於這些場景,差分函數是完美的。

讓我們通過一個簡單的場景開始不同的旅程:您已經收到系統中所有用戶 ID 的列表,以及帳戶處於活動狀態的用戶列表。 你如何找到不活動的ID? 很簡單吧?
const _ = require('lodash'); const allUserIds = [1, 3, 4, 2, 10, 22, 11, 8]; const activeUserIds = [1, 4, 22, 11, 8]; const inactiveUserIds = _.difference(allUserIds, activeUserIds); console.log(inactiveUserIds); // [ 3, 2, 10 ] 如果在更現實的環境中發生這種情況,您必須使用對像數組而不是簡單的基元呢? 好吧,Lodash 有一個很好的differenceBy()方法!
const allUsers = [ { id: 1, name: 'Phil' }, { id: 2, name: 'John' }, { id: 3, name: 'Rogg' }, ]; const activeUsers = [ { id: 1, name: 'Phil' }, { id: 2, name: 'John' }, ]; const inactiveUsers = _.differenceBy(allUsers, activeUsers, 'id'); console.log(inactiveUsers); // [ { id: 3, name: 'Rogg' } ]整潔,對吧?!
和差異一樣,Lodash 中還有其他的方法來處理常見的集合操作:並集、交集等。
展平陣列
扁平化數組的需要經常出現。 一個用例是您收到了一個 API 響應,需要在復雜的嵌套對象/數組列表上應用一些map()和filter()組合來提取用戶 ID,現在您只剩下數組的數組。 這是描述這種情況的代碼片段:
const orderData = { internal: [ { userId: 1, date: '2021-09-09', amount: 230.0, type: 'prepaid' }, { userId: 2, date: '2021-07-07', amount: 130.0, type: 'prepaid' }, ], external: [ { userId: 3, date: '2021-08-08', amount: 30.0, type: 'postpaid' }, { userId: 4, date: '2021-06-06', amount: 330.0, type: 'postpaid' }, ], }; // find user ids that placed postpaid orders (internal or external) const postpaidUserIds = []; for (const [orderType, orders] of Object.entries(orderData)) { postpaidUserIds.push(orders.filter((order) => order.type === 'postpaid')); } console.log(postpaidUserIds); 你能猜出postPaidUserIds現在是什麼樣子嗎? 提示:太噁心了!
[ [], [ { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' }, { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' } ] ] 現在,如果您是一個明智的人,您不想編寫自定義邏輯來提取訂單對象並將它們整齊地排列在數組中的一行中。 只需使用flatten()方法並享受葡萄:
const flatUserIds = _.flatten(postpaidUserIds); console.log(flatUserIds); /* [ { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' }, { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' } ] */ 請注意flatten()只深入一層。 也就是說,如果您的對象卡在兩層、三層或更多層深, flatten()它們會讓您失望。 在這些情況下,Lodash 有flattenDeep()方法,但請注意,在非常大的結構上應用此方法會減慢速度(因為在幕後,有一個遞歸操作在起作用)。

對象/數組是否為空?
由於“假”值和類型在 JavaScript 中的工作方式,有時像檢查空性這樣簡單的事情會導致存在恐懼。

如何檢查數組是否為空? 您可以檢查其length是否為0 。 現在,您如何檢查對像是否為空? 嗯……等一下! 這就是那種不安的感覺開始的地方,那些包含[] == false和{} == false類內容的 JavaScript 示例開始在我們腦海中盤旋。 當面臨交付功能的壓力時,像這樣的地雷是您最不需要的東西——它們會使您的代碼難以理解,並且會給您的測試套件帶來不確定性。
處理缺失數據
在現實世界中,數據聽我們的; 無論我們多麼想要它,它都很少精簡和理智。 一個典型的例子是在作為 API 響應接收的大型數據結構中缺少空對象/數組。

假設我們收到以下對像作為 API 響應:
const apiResponse = { id: 33467, paymentRefernce: 'AEE3356T68', // `order` object missing processedAt: `2021-10-10 00:00:00`, }; 如圖所示,我們通常會在 API 的響應中獲得一個order對象,但情況並非總是如此。 那麼,如果我們有一些依賴於這個對象的代碼呢? 一種方法是防禦性編碼,但根據order對象的嵌套方式,如果我們希望避免運行時錯誤,我們很快就會編寫非常難看的代碼:
if ( apiResponse.order && apiResponse.order.payee && apiResponse.order.payee.address ) { console.log( 'The order was sent to the zip code: ' + apiResponse.order.payee.address.zipCode ); }是的,寫起來很醜,讀起來很醜,維護起來也很醜,等等。 值得慶幸的是,Lodash 有一種直接的方法來處理這種情況。
const zipCode = _.get(apiResponse, 'order.payee.address.zipCode'); console.log('The order was sent to the zip code: ' + zipCode); // The order was sent to the zip code: undefined 還有一個很棒的選擇是提供默認值而不是為丟失的東西設置undefined :
const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA'); console.log('The order was sent to the zip code: ' + zipCode2); // The order was sent to the zip code: NA 我不了解你,但get()是讓我眼中流淚的事情之一。 這不是什麼華而不實的。 沒有需要記住的參考語法或選項,但看看它可以減輕多少集體痛苦!
去抖動
如果您不熟悉,去抖動是前端開發中的一個常見主題。 這個想法是有時不是立即啟動一個動作而是在一段時間後(通常是幾毫秒)啟動一個動作是有益的。 這意味著什麼? 這是一個例子。

想像一個帶有搜索欄的電子商務網站(好吧,現在任何網站/網絡應用程序!)。 為了獲得更好的 UX,我們不希望用戶必須按 Enter(或更糟的是,按“搜索”按鈕)才能根據他們的搜索詞顯示建議/預覽。 但顯而易見的答案是有點加載:如果我們為搜索欄的onChange()添加一個事件偵聽器,並為每次擊鍵觸發 API 調用,我們就會為我們的後端創造一個噩夢; 會有太多不必要的調用(例如,如果搜索“白色地毯刷”,總共有18個請求!)並且幾乎所有這些都無關緊要,因為用戶輸入還沒有完成。
答案在於去抖動,其想法是:不要在文本更改後立即發送 API 調用。 等待一段時間(例如 200 毫秒),如果到那時還有另一個擊鍵,請取消較早的時間計數並再次開始等待。 因此,只有當用戶暫停時(或者因為他們正在思考,或者因為他們已經完成並且他們期望得到一些響應),我們才會向後端發送 API 請求。
我描述的整體策略很複雜,我不會深入研究定時器管理和取消的同步; 但是,如果您使用的是 Lodash,實際的去抖動過程非常簡單。
const _ = require('lodash'); const axios = require('axios'); // This is a real dogs' API, by the way! const fetchDogBreeds = () => axios .get('https://dog.ceo/api/breeds/list/all') .then((res) => console.log(res.data)); const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // after one second debouncedFetchDogBreeds(); // shows data after some time 如果你在想setTimeout()我會做同樣的工作,嗯,還有更多! Lodash 的 debounce 具有許多強大的功能; 例如,您可能希望確保去抖動不是無限期的。 也就是說,即使每次函數即將觸發(從而取消整個過程)時都有一個擊鍵,您可能希望確保 API 調用在兩秒鐘後仍然進行。 為此,Lodash debounce debounce()有maxWait選項:
const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // debounce for 250ms, but send the API request after 2 seconds anyway請查看官方文檔以進行更深入的了解。 它們充滿了非常重要的東西!
從數組中刪除值
我不了解您,但我討厭編寫用於從數組中刪除項目的代碼。 首先,我必須獲取項目的索引,檢查索引是否實際有效,如果有效,則調用splice()方法,等等。 我永遠記不住語法,因此需要一直查資料,最後,我有一種嘮叨的感覺,我讓一些愚蠢的錯誤潛入。

const greetings = ['hello', 'hi', 'hey', 'wave', 'hi']; _.pull(greetings, 'wave', 'hi'); console.log(greetings); // [ 'hello', 'hey' ]請注意兩點:
- 原始數組在此過程中發生了變化。
-
pull()方法刪除所有實例,即使有重複。
還有另一個稱為pullAll()相關方法,它接受一個數組作為第二個參數,從而可以更輕鬆地一次刪除多個項目。 當然,我們可以將pull()與擴展運算符一起使用,但請記住,Lodash 出現的時候,擴展運算符甚至還不是該語言的提案!
const greetings2 = ['hello', 'hi', 'hey', 'wave', 'hi']; _.pullAll(greetings2, ['wave', 'hi']); console.log(greetings2); // [ 'hello', 'hey' ]元素的最後一個索引
JavsScript 的原生indexOf()方法很酷,除非您有興趣從相反方向掃描數組! 再一次,是的,您可以編寫一個遞減循環並找到元素,但為什麼不使用更優雅的技術呢?

這是一個使用lastIndexOf()方法的快速 Lodash 解決方案:
const integers = [2, 4, 1, 6, -1, 10, 3, -1, 7]; const index = _.lastIndexOf(integers, -1); console.log(index); // 7不幸的是,這種方法沒有變體,我們可以在其中查找複雜的對象,甚至可以傳遞自定義查找函數。
壓縮。 解壓!

除非您曾使用過 Python,否則 zip/unzip 是一種您在作為 JavaScript 開發人員的整個職業生涯中可能永遠不會注意到或想像的實用程序。 也許有一個很好的理由:很少像filter()等那樣迫切需要 zip/unzip。但是,它是最好的鮮為人知的實用程序之一,可以幫助您在某些情況下創建簡潔的代碼.
與聽起來相反,zip/unzip 與壓縮無關。 相反,它是一個分組操作,其中相同長度的數組可以轉換為單個數組數組,其中相同位置的元素打包在一起 ( zip() ) 並返回 ( unzip() )。 是的,我知道,試圖用文字湊合會變得模糊,所以讓我們看一些代碼:
const animals = ['duck', 'sheep']; const sizes = ['small', 'large']; const weight = ['less', 'more']; const groupedAnimals = _.zip(animals, sizes, weight); console.log(groupedAnimals); // [ [ 'duck', 'small', 'less' ], [ 'sheep', 'large', 'more' ] ] 原來的三個數組被轉換成一個只有兩個數組的數組。 這些新數組中的每一個都代表一個動物,所有動物都集中在一個地方。 因此,指數0告訴我們它是哪種動物,指數1告訴我們它的大小,而指數2告訴我們它的重量。 因此,現在可以更輕鬆地處理數據。 一旦你對數據應用了你需要的任何操作,你可以使用unzip()再次分解它並將它發送回原始源:
const animalData = _.unzip(groupedAnimals); console.log(animalData); // [ [ 'duck', 'sheep' ], [ 'small', 'large' ], [ 'less', 'more' ] ]zip/unzip 實用程序不會在一夜之間改變您的生活,但總有一天會改變您的生活!
結論
(我把本文用到的所有源碼都放在這裡了,大家可以直接在瀏覽器上試試!)
Lodash 文檔充滿了會讓您大吃一驚的示例和函數。 在 JS 生態系統中受虐狂似乎越來越多的時代,Lodash 就像一股新鮮空氣,我強烈建議您在您的項目中使用這個庫!
