我们着眼于使用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对操作对象的数组。由于这些数组运算符不会修改调用数组的状态,所以我们可以有效地使用它们而不用担心副作用。使用从函数式编程中借鉴的技巧,我们可以编写出功能强大的表达式数组运算符 通过函数委托,我们可以选择显式函数名来创建可读代码。
上一篇:css教程下载,CSS3各大网站分享按钮 带网站Logo小图标
下一篇:没有了