使用JavaScript對象數組進行函數式編程

2020-12-16 智能甄選

我們著眼於使用map,filter和reduce來操縱對象數組,使用從函數式編程中借用的技術。

數據操作是任何JavaScript應用程式中的常見任務。幸運的是,新的陣列處理運營商map,filter以及reduce廣泛的支持。雖然這些功能的文檔是足夠的,但它通常會顯示非常基本的實現用例。在日常使用中,我們通常需要使用這些方法來處理數據對象的數組,這是文檔中缺乏的場景。另外,這些操作符經常被用功能性語言來看,並為JavaScript帶來了一種新的透視功能,即通過具有功能性觸摸的對象進行迭代。

在這篇文章中,我們將打破使用的幾個例子map,reduce和filter操作對象的數組。通過這些例子,我們將學習這些方法的強大程度,同時了解它們與函數式編程的關係。

功能編程:好的部分

函數式編程有許多概念超出了我們要實現的範圍。在本文中,我們將討論一些絕對的基礎知識。

在整個示例中,我們將傾向於使用多個語句的單個表達式。這意味著我們將大大減少使用的變量數量,並儘可能地利用函數組合和方法連結。這種編程風格減少了維護狀態的需要,這與功能性編程實踐相一致。

使用的一個共同利益map,並filter為每個運營商創建新的數組。通過創建新數組而不是修改或改變現有數據,我們的代碼將更容易推理,因為變異數據可能會導致意想不到的副作用。

最後,我們將利用每個運算符都是更高階函數的事實。簡而言之,每個運營商都接受一個回調函數作為參數。回調函數允許我們通過函數委託來定製行為,這是一個強大的代碼靈活性和可讀性的工具。

這裡的想法並不是「功能齊全」,而是在方便時利用概念。如果這些概念與您聯繫在一起,則可以下載功能性編程中常用的JavaScript方法/函數列表的備忘單。該函數式編程小抄是一個偉大的快速參考,以保持得心應手。

無處不在的箭頭/函數表達式

當來自較舊的JavaScript庫時,「胖箭頭」可能看起來有些陌生。此=>箭頭運算符用於減少編寫函數所需的代碼量。當我們需要一個我們可以使用的簡單函數時,不必用return語句顯式地寫函數()Identifier => Expression。這有助於使我們的代碼更易於閱讀,特別是在使用高階函數時,以函數作為參數的方法。.map,.reduce並且.filter都是更高階的函數。

沒有函數表達式,我們可能會這樣寫:

let cart = [{ name: "Drink", price: 3.12 }, { name: "Steak", price: 45.15}, { name: "Drink", price: 11.01}];let steakOrders = cart.filter(function(obj) { return obj.name === "Steak"});// { name: "Steak", price: 45.15 }

使用箭頭運算符,我們可以考慮編寫相同的代碼,省略如下例所示的函數:

let steakOrders = cart.filter((obj) => { return obj.name === "Steak" });// { name: "Steak", price: 45.15 }

但是,我們可以更進一步,因為大多數表達式在使用箭頭運算符時已經隱含。代碼可以進一步縮小:

et steakOrders = cart.filter(obj => obj.name === "Steak");// { name: "Steak", price: 45.15 }

在這個例子中,我們可以看到箭頭幫助在過濾器語句中定義了一個簡潔的函數。現在我們已經看到了箭頭如何改進表達式的寫法,讓我們進一步了解使用該filter方法。

過濾對象數組

該filter方法創建一個新數組,其中包含所有通過由提供的函數實現的測試的元素。當針對對象使用過濾器方法時,我們可以根據對象上的一個或多個屬性創建一個受限制的集合。

在這個例子中,我們通過傳入一個函數表達式來測試名稱屬性的值,以得到名稱為「Steak」的集合中的對象obj => obj.name === "Steak":

let cart = [{ name: "Drink", price: 3.12 }, { name: "Steak", price: 45.15}, { name: "Drink", price: 11.01}];let steakOrders = cart.filter(obj => obj.name === "Steak");// { name: "Steak", price: 45.15 }

考慮到我們可能需要一部分數據,我們需要在多個屬性值上進行過濾。我們可以通過編寫一個包含多個測試的函數來實現:

let expensiveDrinkOrders =cart.filter(x => x.name === "Drink" && x.price > 10);// { name: "Drink", price: 11.01 }

當對多個值進行過濾時,函數表達式可能會變得冗長,從而使代碼難以一目了然。使用一些技巧,我們可以簡化代碼並使其更易於管理。

解決該問題的一種方法是使用多個過濾器代替&&操作員。通過將多個過濾器連結在一起,我們可以在產生相同結果的同時使聲明更易於推理。

let expensiveDrinkOrders = cart.filter(x => x.name === "Drink").filter(x => x.price > 10);// { name: "Drink", price: 11.01 }

另一種表達複雜過濾器的方法是創建一個命名函數。這將使我們能夠以「人類可讀」的方式編寫過濾器:

const drinksGreaterThan10 =obj => obj.name === "Drink" && obj.price > 10;let result = cart.filter(drinksGreaterThan10);

雖然這確實按預期工作,但價格值是硬編碼的。我們可以通過允許使用參數在運行時設置價格來優化可讀性和靈活性。

起初,將參數添加obj到它讀取的參數可能看起來很直觀,變量價格(obj, cost)在哪裡cost:

const drinksGreaterThan =(obj, cost) => obj.name === "Drink" && obj.price > cost;let result = cart.filter(drinksGreaterThan(10)); // Error

用JavaScript編寫

不幸的是,這會產生一個錯誤,因為過濾器需要一個帶有單個參數的函數,現在我們試圖使用兩個參數。為了正確地滿足參數,我們需要將過濾語句寫為.filter(x => drinksGreaterThan(x, 10))。

為了提供更好的解決方案,我們可以使用稱為currying的函數式編程技術。Currying允許將具有多個參數的函數轉換為函數序列,從而允許我們創建與其他函數籤名的奇偶校驗。

讓我們將cost參數移到一個函數表達式中,並drinksGreaterThan使用currying 重寫該函數。這只是巧妙地使用高階函數,其中drinksGreatThan變成了一個接受成本並返回另一個函數的函數,該函數接受obj:

const drinksGreaterThan =cost => obj => obj.name === "Drink" && obj.price > cost;let result = cart.filter(drinksGreaterThan(10));// { name: "Drink", price: 11.01 }

完成的功能為我們提供了一個具有最大可讀性和可重用性的命名過濾器。

映射對象

該 map 方法是轉換和投影數據的重要工具。該map方法創建一個新的數組,其結果是對調用數組中的每個元素調用一個提供的函數。

在我們使用外部數據源的情況下,我們可能會提供比所需更多的數據。我們可以使用map我們認為合適的方式來投影數據,而不是處理完整的數據集。在以下示例中,我們將收到包含多個屬性的數據,其中大多數屬性在當前視圖中未使用。

初始對象包含: id, name, price, cost,和 size:

let jsonData = [{ id: 1, name: "Soda", price: 3.12, cost: 1.04, size: "4oz", }, { id: 2, name: "Beer", price: 6.50, cost: 2.45, size: "8oz" }, { id: 3, name: "Margarita", price: 12.99, cost: 4.45, size: "12oz" }];

我們認為,我們只會使用名稱和價格屬性,因此我們將使用它 map 來構建新的數據集:

let drinkMenu = jsonData.map(x => ({name: x.name, price: x.price}));// [{"name":"Soda","price":3.12}, {"name":"Beer","price":6.5}, {"name":"Margarita","price":12.99}]

使用時map,我們可以將每個項目的值分配給僅具有名稱和價格屬性的新對象。

如果我們關心map函數表達式的可讀性,我們可以將該函數分配給一個變量。這種抽象並不改變map操作方式,但是,對於那些不熟悉域的人來說,代碼的意圖會變得更加清晰。

通過創建一個名為的獨特方法toMenuItem,我們可以立即給我們的代碼上下文。該 map 聲明變成了自我記錄,因為它可以朗讀,「JSON數據,映射到菜單項」。

const toMenuItem = x => ({name: x.name, price: x.price});let drinkMenu = jsonData.map(toMenuItem);// [{"name":"Soda","price":3.12}, {"name":"Beer","price":6.5}, {"name":"Margarita","price":12.99}]

繼續這個例子,我們將map再次使用來應用一個值轉換。假設我們想將現有的菜單價格從美元轉換為歐元。由於 map 不會改變初始數組,所以我們不必擔心drinkMenu由於我們的轉換而導致實例更改的狀態。

讓我們使用一個類似的函數來轉換價格值,除了這次我們將保留對象可用的所有屬性。為了確保我們複製每個值,我們將使用...spread運算符:

const drinkMenuEuro = drinkMenu.map(x => ({...x, price: (x.price * 0.81).toFixed(2)})// [{"name":"Soda","price":"2.53"},{"name":"Beer","price":"5.27"},{"name":"Margarita","price":"10.52"}]

在這個例子中,...x(擴展運算符)為我們做了很多。這一小段代碼可以確保我們複製整個對象及其屬性,同時只修改價格值。使用spread運算符的好處是我們可以稍後添加其他屬性,drinkMenu並且它們將被drinkMenuEuro自動包含 。

減少與對象

Reduce是一個使用不充分,有時會被誤解的數組運算符。該reduce方法對累加器和數組中的每個元素(從左到右)應用一個函數,以將其減少為單個值。

當使用reduce對象的數組時,累加器值表示前一次迭代產生的對象。如果我們需要獲取對象屬性的總值(即obj.price),我們首先需要將該屬性映射到單個值:

let cart = [{ name: "Soda", price: 3.12 }, { name: "Margarita", price: 12.99}, { name: "Beer", price: 6.50}];let totalPrice = cart.reduce((acc,next) => acc.price + next.price);// NaN because acc cannot be both an object and numberlet totalPrice = cart.map(obj => obj.price).reduce((acc, next) => acc + next);// 22.61

在前面的例子中,我們看到當我們減少一個對象數組時,累加器就是對象。該reduce方法對於正確條件下的對象非常有用。我們不用試圖總結一個對象的價格,而是使用reduce來查找價格最高的對象:

let mostExpensiveItem = cart.reduce((acc, next) => acc.price > next.price ? acc : next);// { name: "Margarita", price: 12.99}

在這裡,我們利用功能 reduce 來給我們最大的目標。

我們可以使用函數委託來描述我們的意圖。這將有助于澄清reduce操作符中的表達式。我們最終的功能是「以最大的價格降低」:

let byGreatestPrice = (item, next) => item.price > next.price ? item : next;let mostExpensiveItem = cart.reduce(byGreatestPrice);// { name: "Margarita", price: 12.99}

結論

在這篇文章中,我們討論了使用map,reduce以及filter對操作對象的數組。由於這些數組運算符不會修改調用數組的狀態,所以我們可以有效地使用它們而不用擔心副作用。使用從函數式編程中借鑑的技巧,我們可以編寫出功能強大的表達式數組運算符 通過函數委託,我們可以選擇顯式函數名來創建可讀代碼。

相關焦點

  • 走近 (javascript, 函數式)
    其中,我們把一類相關的數據和命令封裝在一起,形成了類和對象,形成了面向對象的編程方式。函數式編程卻屬於聲明式的編程方式,這種範式會描述一系列的操作,而不去暴露它們是如何實現的,以及數據是如何從中間穿過。在這裡,「動詞」是第一詞彙,我們更加注重於描述不同動作之間的組合和順序。比如,我們需要將一個數組的每個數平方以後,找出其中的奇數,並生成新的數組。
  • 在JavaScript函數式編程裡使用Map和Reduce方法
    英文原文:Using Map and Reduce in Functional JavaScript   唐李川所有人都談論道workflows支持ECMAScript6裡出現的令人吃驚的新特性,因此我們很容易忘掉ECMAScript5帶給我們一些很棒的工具方法來支持在JavaScript裡進行函數編程,這些工具方法我們現在可以使用了。
  • 一文帶你了解什麼是JavaScript 函數式編程?
    函數式編程在前端已經成為了一個非常熱門的話題。在最近幾年裡,我們看到非常多的應用程式代碼庫裡大量使用著函數式編程思想。本文將略去那些晦澀難懂的概念介紹,重點展示在 JavaScript 中到底什麼是函數式的代碼、聲明式與命令式代碼的區別、以及常見的函數式模型都有哪些?
  • [翻譯]淺談JavaScript中的高階函數
    高階函數的採用使得JavaScript適合用來做函數式編程。在JavaScript中,高階函數的使用隨處可見。如果你已經用JavaScript寫過一陣子的代碼,那麼你可能已經在不知情的情況下使用過它了。為了完全理解這個概念,你首先要了解什麼是函數式編程以及頭等函數的概念。
  • JavaScript 數組操作函數總結
    不過不會針對每個辦法都進行一下總結,只是針對一些比較常用的做個備註一下。這裡總結到的 js 數組操作函數有:push,pop,join,shift,unshift,slice,splice,concat(1)push 和 pop這兩個函數都是對數組從尾部進行壓入或彈出操作。
  • javascript函數式編程系列(三)--柯理化和lodash
    如果你看過我之前寫的《javascript函數式編程系列(二)--理解純函數》或者在你的腦海裡已經記住了下面這張圖,如圖1。我想你已經知道,我們這一篇內容講的就是函數式編程中的函數柯理化。當然了,在這篇中我們也將大致介紹一下lodash.js。畢竟理解了輪子是怎麼造的,在實際開發中我們也沒必要重複造輪子了。做一件事情要有目的,學習也不例外。
  • 如何使用JavaScript -面向對象編程
    一本書、一輛汽車、一個人都可以是對象,一個資料庫、一張網頁、一個與遠程伺服器的連接也可以是對象。當實物被抽象成對象,實物之間的關係就變成了對象之間的關係,從而就可以模擬現實情況,針對對象進行編程。(2) 對象是一個容器,封裝了屬性(property)和方法(method)。
  • 給JavaScript 開發者講講函數式編程 - OSCHINA - 中文開源技術...
    從那時起,我開始更深地了解函數式編程並且我覺得應該為那些總能聽到它但不知道究竟是什麼的新人做一點事情。無論在什麼時候出現一個錯誤,你都能夠通過每個函數追溯到問題的緣由。在面向對象編程中,這通常會相當的複雜,因為你一般情況下並不知道引發改問題的對象的其他狀態。
  • 函數式編程
    ,我們會看到如下函數式編程的長相:函數式編程的三大特性:immutable data 不可變數據:像Clojure一樣,默認上變量是不可變的,如果你要改變變量,你需要把變量copy出去修改。函數式編程的幾個技術map & reduce :這個技術不用多說了,函數式編程最常見的技術就是對一個集合做Map和Reduce操作。這比起過程式的語言來說,在代碼上要更容易閱讀。
  • 最強大、最牛逼的javascript視頻免費發布啦
    javascript視頻教程之第一季《ECMA5核心+設計模式》,盡請期待javascript視頻教程第二季《WEB+最佳實戰》課程目錄:1_尚學堂科技_javascript視頻教程_白賀翔_初識javascript2_尚學堂科技_javascript視頻教程_白賀翔_變量和數據類型3_尚學堂科技_javascript視頻教程_白賀翔_變量的自動轉換和語句
  • 再談JavaScript面向對象編程
    當然還可以var array1= [ 1,2,3];  數組也是一個對象。其他關於對象的基本的概念的描述,還是請各位親們參見陳皓《Javascript 面向對象編程》文章。正是因為所有的數組對象(通過[]來創建的)都包含有一個指向同一個具有push,reverse等方法對象(Array.prototype)的__proto__成員,才使得這些數組對象可以使用push,reverse等方法。
  • 大前端進擊之路(一):函數式編程
    函數式編程概念一、什麼是函數式編程函數式編程(Functional Programming, FP),是一種編程風格,也可以認為是一種思維模式,和面向過程、面向對象是並列的關係。函數式編程是對運算過程的抽象,函數指的並不是程序中的函數或者方法,而是數學中的函數映射關係,例如:y=cos(x),是y和x的關係。
  • JavaScript之Promise對象
    javascript是單線程語言,所以都是同步執行的,要實現異步就得通過回調函數的方式,但是過多的回調會導致回調地獄,代碼既不美觀,也不易維護,所以就有了promise;Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。
  • 有趣的JavaScript原生數組函數
    數組對象繼承自Object.prototype,對操作符返回的數組類型返回對象而不是array。然而,[]實例,數組也返回true。變量,類數組對象的實現更複雜,例如字符串對象,參數對象,參數對象不是數組的實例,但有長度屬性,並能通過索引取值,所以能像多個相同進行循環操作。在這裡,我將複習一些複製原型的方法,並探索這些方法的用法。
  • 每日一課 | JavaScript中的數組
    JavaScript中的數組具有length屬性,該屬性返回該數組的大小。,並且未在數組中添加任何元素。 1)同時聲明和填充數組var array=[10,20,30];console.log(array);2)使用數組構造函數聲明數組:在此方法中,我們使用數組構造函數聲明數組,然後使用索引填充此數組。
  • 優秀程式設計師通過簡單代碼,窺探電腦編程中強大的數組操作功能
    優秀程式設計師通過簡單代碼,窺探電腦編程中強大的數組操作功能。程式語言中,數組是一個非常重要的概念,也是一種很常用的類型。本文中通過javascript語言的代碼實例,展現編程中數組的魅力。在javascript語言中,數組Array類型是一種引用類型,可以保存任何數據類型的數據。如字符串,數值,對象等。
  • 函數式編程聖經
    如果函數返回了一個新的數組,而沒有改變原有的值,那函數式上帝很高興。可是這個宇宙的人類總是抱怨函數式編程太難,看到這裡的上帝不願意改變,有很多人都穿越到編號為S-87那個所謂「面向對象」的宇宙去了。上帝嘆了口氣:「你們吶,目光短淺,摩爾定律已經失效,多核時代已經來臨,我們函數式編程天生是為並發編程而生的啊,你看看函數沒有side effect,不共享變量,可以安全地調度到任何一個CPU core上去運行,沒有煩人的加鎖問題,多好啊!」過了兩天,隔壁 S-87 宇宙的面向對象上帝來串門了。
  • Java如何支持函數式編程?
    在很長的一段時間裡,Java一直是面向對象的語言,一切皆對象,如果想要調用一個函數,函數必須屬於一個類或對象,然後在使用類或對象進行調用。但是在其它的程式語言中,如JS、C++,我們可以直接寫一個函數,然後在需要的時候進行調用,既可以說是面向對象編程,也可以說是函數式編程。從功能上來看,面向對象編程沒什麼不好的地方,但是從開發的角度來看,面向對象編程會多寫很多可能是重複的代碼行。
  • 函數式編程,真香
    最近在研究函數式編程,真的是在學習的過程中感覺自己的思維提升了很多,抽象能力大大的提高了,讓我深深的感受到了函數式編程的魅力。所以我打算後面用 5 到 8 篇的篇幅,詳細的介紹一下函數式編程的思想,基礎、如何設計、測試等。今天這篇文章主要介紹函數式編程的思想。函數式編程有用嗎?什麼是函數式編程?函數式編程的優點。
  • 初中生數學知識,初探計算機編程中數組的奧秘,編程真的難嗎?
    利用初中生的數學知識,初探計算機編程中數組的奧秘,你覺得編程真的難嗎?今日目標:通過簡單的數學運算,深入了解計算機編程中javascript數組的歸併應用。數組歸併,通過某種函數的執行,對數組的所有元素進行特定的操作處理,並返回符合條件的單個值過程。本例中,主要了解程式設計師常用的兩個方法:reduce()方法和reduceRight()方法。這兩種方法功能基本相同,只是執行順序相反,最終結果返回值是一致的。