使用 map 方法从数组中提取数据
目前为止,我们已经学会了使用纯函数来避免程序中的副作用。 此外,我们已经看到函数的值仅取决于其输入参数。
这仅仅是个开始。 顾名思义,函数式编程以函数理论为中心。
能够将它们作为参数传递给其他函数,从另一个函数返回一个函数是有意义的。 函数在 JavaScript 中被视为 First Class Objects,它们可以像任何其他对象一样使用。 它们可以保存在变量中,存储在对象中,也可以作为函数参数传递。
让我们从一些简单的数组函数开始,这些函数是数组对象原型上的方法。 在本练习中,我们来了解下数组的 map 方法(即 Array.prototype.map())。
请记住,map方法是迭代数组中每一项的方式之一。 在对每个元素应用回调函数后,它会创建一个新数组(不改变原来的数组)。 它这样做时没有改变原始数组。
当调用回调函数时,传入了三个参数。 第一个参数是当前正在处理的数组项。 第二个参数是当前数组项的索引值,第三个参数是在其上调用 map 方法的数组。
看下在 users 上使用 map 方法的例子,返回了一个新数组只包含了用户的名字。 为了简化,例子里只使用了回调函数的第一个参数。
const users = [
{ name: 'John', age: 34 },
{ name: 'Amy', age: 20 },
{ name: 'camperCat', age: 10 }
];
const names = users.map(user => user.name);
console.log(names);//`[ 'John', 'Amy', 'camperCat' ]`
watchList 数组保存了包含一些电影信息的对象。 在 watchList 上使用 map,将一个新的对象数组赋值给 ratings 变量。 新数组中的每个电影都只能有一个值为电影名称的 title 键,和一个值为 IMDB 评级的 rating 键。 目前编辑器中的代码是使用 for 循环实现,你应该使用 map 表达式替换循环功能。
// 全局变量
const watchList = [
{
"Title": "Inception",
"Year": "2010",
"Rated": "PG-13",
"Released": "16 Jul 2010",
"Runtime": "148 min",
"Genre": "Action, Adventure, Crime",
"Director": "Christopher Nolan",
"Writer": "Christopher Nolan",
"Actors": "Leonardo DiCaprio, Joseph Gordon-Levitt, Elliot Page, Tom Hardy",
"Plot": "A thief, who steals corporate secrets through use of dream-sharing technology, is given the inverse task of planting an idea into the mind of a CEO.",
"Language": "English, Japanese, French",
"Country": "USA, UK",
"Awards": "Won 4 Oscars. Another 143 wins & 198 nominations.",
"Poster": "http://ia.media-imdb.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1_SX300.jpg",
"Metascore": "74",
"imdbRating": "8.8",
"imdbVotes": "1,446,708",
"imdbID": "tt1375666",
"Type": "movie",
"Response": "True"
},
{
"Title": "Interstellar",
"Year": "2014",
"Rated": "PG-13",
"Released": "07 Nov 2014",
"Runtime": "169 min",
"Genre": "Adventure, Drama, Sci-Fi",
"Director": "Christopher Nolan",
"Writer": "Jonathan Nolan, Christopher Nolan",
"Actors": "Ellen Burstyn, Matthew McConaughey, Mackenzie Foy, John Lithgow",
"Plot": "A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival.",
"Language": "English",
"Country": "USA, UK",
"Awards": "Won 1 Oscar. Another 39 wins & 132 nominations.",
"Poster": "http://ia.media-imdb.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE@._V1_SX300.jpg",
"Metascore": "74",
"imdbRating": "8.6",
"imdbVotes": "910,366",
"imdbID": "tt0816692",
"Type": "movie",
"Response": "True"
},
{
"Title": "The Dark Knight",
"Year": "2008",
"Rated": "PG-13",
"Released": "18 Jul 2008",
"Runtime": "152 min",
"Genre": "Action, Adventure, Crime",
"Director": "Christopher Nolan",
"Writer": "Jonathan Nolan (screenplay), Christopher Nolan (screenplay), Christopher Nolan (story), David S. Goyer (story), Bob Kane (characters)",
"Actors": "Christian Bale, Heath Ledger, Aaron Eckhart, Michael Caine",
"Plot": "When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to terms with one of the greatest psychological tests of his ability to fight injustice.",
"Language": "English, Mandarin",
"Country": "USA, UK",
"Awards": "Won 2 Oscars. Another 146 wins & 142 nominations.",
"Poster": "http://ia.media-imdb.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_SX300.jpg",
"Metascore": "82",
"imdbRating": "9.0",
"imdbVotes": "1,652,832",
"imdbID": "tt0468569",
"Type": "movie",
"Response": "True"
},
{
"Title": "Batman Begins",
"Year": "2005",
"Rated": "PG-13",
"Released": "15 Jun 2005",
"Runtime": "140 min",
"Genre": "Action, Adventure",
"Director": "Christopher Nolan",
"Writer": "Bob Kane (characters), David S. Goyer (story), Christopher Nolan (screenplay), David S. Goyer (screenplay)",
"Actors": "Christian Bale, Michael Caine, Liam Neeson, Katie Holmes",
"Plot": "After training with his mentor, Batman begins his fight to free crime-ridden Gotham City from the corruption that Scarecrow and the League of Shadows have cast upon it.",
"Language": "English, Urdu, Mandarin",
"Country": "USA, UK",
"Awards": "Nominated for 1 Oscar. Another 15 wins & 66 nominations.",
"Poster": "http://ia.media-imdb.com/images/M/MV5BNTM3OTc0MzM2OV5BMl5BanBnXkFtZTYwNzUwMTI3._V1_SX300.jpg",
"Metascore": "70",
"imdbRating": "8.3",
"imdbVotes": "972,584",
"imdbID": "tt0372784",
"Type": "movie",
"Response": "True"
},
{
"Title": "Avatar",
"Year": "2009",
"Rated": "PG-13",
"Released": "18 Dec 2009",
"Runtime": "162 min",
"Genre": "Action, Adventure, Fantasy",
"Director": "James Cameron",
"Writer": "James Cameron",
"Actors": "Sam Worthington, Zoe Saldana, Sigourney Weaver, Stephen Lang",
"Plot": "A paraplegic marine dispatched to the moon Pandora on a unique mission becomes torn between following his orders and protecting the world he feels is his home.",
"Language": "English, Spanish",
"Country": "USA, UK",
"Awards": "Won 3 Oscars. Another 80 wins & 121 nominations.",
"Poster": "http://ia.media-imdb.com/images/M/MV5BMTYwOTEwNjAzMl5BMl5BanBnXkFtZTcwODc5MTUwMw@@._V1_SX300.jpg",
"Metascore": "83",
"imdbRating": "7.9",
"imdbVotes": "876,575",
"imdbID": "tt0499549",
"Type": "movie",
"Response": "True"
}
];
//原来是这样写
// const ratings = [];
// for (let i = 0; i < watchList.length; i++) {
// ratings.push({title: watchList[i]["Title"], rating: watchList[i]["imdbRating"]});
// }
//函数式编程写法:
const ratings = watchList.map(item => ({
title: item["Title"],
rating: item["imdbRating"]
}));
console.log(JSON.stringify(ratings));
//[{"title":"Inception","rating":"8.8"},{"title":"Interstellar","rating":"8.6"},{"title":"The Dark Knight","rating":"9.0"},{"title":"Batman Begins","rating":"8.3"},{"title":"Avatar","rating":"7.9"}]
使用 filter 方法从数组中提取数据
另一个有用的数组方法是 filter()(即 Array.prototype.filter())。
filter 接收一个回调函数,将回调函数内的逻辑应用于数组的每个元素,新数组包含根据回调函数内条件返回 true 的元素。
换言之,它根据传递给它的函数过滤数组。 和 map 一样,filter 不会改变原始数组。
回调函数接收三个参数: 第一个参数是当前正在被处理的元素。 第二个参数是这个元素的索引, 第三个参数是在其上调用 filter方法的数组。
看下在 users 上使用 filter 方法的例子,返回了一个包含了 30 岁以下的用户新数组。 为了简化,例子里只使用了回调函数的第一个参数。
const users = [
{ name: 'John', age: 34 },
{ name: 'Amy', age: 20 },
{ name: 'camperCat', age: 10 }
];
const usersUnder30 = users.filter(user => user.age < 30);
console.log(usersUnder30);
控制台将显示值 [ { name: 'Amy', age: 20 }, { name: 'camperCat', age: 10 } ]
watchList 变量中包含一组存有多部电影信息对象。 结合 filter 和 map 返回一个 watchList 只包含 title 和 rating 属性的新数组。 新数组只包含 imdbRating 值大于或等于 8.0 的对象。 请注意,rating 值在对象中保存为字符串,你可能需要将它转换成数字来执行运算。
//全局变量watchlist在第二个代码中给出,下面的例子都不再重复粘贴,太长了!
const filteredList = watchList
.filter(movie => movie.imdbRating >= 8.0)
.map(movie => ({ title: movie["Title"], rating: movie["imdbRating"] }));
console.log(filteredList);
/*[ { title: 'Inception', rating: '8.8' },
{ title: 'Interstellar', rating: '8.6' },
{ title: 'The Dark Knight', rating: '9.0' },
{ title: 'Batman Begins', rating: '8.3' } ]
*/
使用 slice 方法返回数组的一部分
slice 方法可以从已有数组中返回指定元素。 它接受两个参数,第一个规定从何处开始选取,第二个规定从何处结束选取(不包括该元素)。 如果没有传参,则默认为从数组的开头开始到结尾结束,这是复制整个数组的简单方式。 slice 返回一个新数组,不会修改原始数组。
举个例子:
function sliceArray(anim, beginSlice, endSlice) {
return anim.slice(beginSlice, endSlice);
}
const inputAnim = ["Cat", "Dog", "Tiger", "Zebra", "Ant"];
sliceArray(inputAnim, 1, 3);
使用 concat 方法组合两个数组
concat 方法,这是一种在不改变原始数组的前提下,将数组组合成新数组的方法。
function nonMutatingConcat(original, attach) {
return original.concat(attach);
}
const first = [1, 2, 3];
const second = [4, 5];
nonMutatingConcat(first, second);
使用 concat 而不是 push 将元素添加到数组的末尾
函数式编程就是创建和使用具有不变性的函数。
刚刚介绍了 concat 方法,这是一种在不改变原始数组的前提下,将数组组合成新数组的方法。 将 concat 方法与 push 方法做比较。 push 将元素添加到调用它的数组的末尾,这样会改变该数组。
concat 方法可以将新项目添加到数组末尾,而不产生副作用。
修改 nonMutatingPush 函数,用 concat 替代 push 将 newItem 添加到 original 末尾。 该函数应返回一个数组。
function nonMutatingPush(original, newItem) {
return original.concat(newItem);
}
const first = [1, 2, 3];
const second = [4, 5];
nonMutatingPush(first, second);
使用 reduce 方法分析数据
reduce()(即Array.prototype.reduce()),是 JavaScript 所有数组操作中最常用的方法。 几乎可以用reduce方法解决所有数组处理问题。
reduce方法是处理数组更通用的方式,而且filter和map方法都可以当作是reduce的特殊实现。 reduce方法遍历数组中的每个项目并返回单个值(即字符串、数字、对象、数组)。 这是通过在每次迭代中调用一个回调函数来实现的。
回调函数接受四个参数。
- 第一个参数称为
叠加器,它是上一次迭代中回调函数的返回值, - 第二个参数是当前正在处理的数组元素,
- 第三个参数是该参数的索引,
- 第四个参数是在其上调用
reduce方法的数组。
除了回调函数,reduce 还有一个额外的参数做为叠加器的初始值。 如果没有第二个参数,会跳过第一次迭代,第二次迭代给叠加器传入数组的第一个元素。
见下面的例子,给 users 数组使用 reduce 方法,返回所有用户数组的和。 为了简化,例子仅使用了回调函数的第一个参数和第二个参数。
const users = [
{ name: 'John', age: 34 },
{ name: 'Amy', age: 20 },
{ name: 'camperCat', age: 10 }
];
const sumOfAges = users.reduce((sum, user) => sum + user.age, 0);
console.log(sumOfAges);
const users = [
{ name: 'John', age: 34 },
{ name: 'Amy', age: 20 },
{ name: 'camperCat', age: 10 }
];
const usersObj = users.reduce((obj, user) => {
obj[user.name] = user.age;
return obj;
}, {});
console.log(usersObj);
watchList 是包含一些电影信息的对象。 使用 reduce 查找由 Christopher Nolan 导演的电影的 IMDB 评级平均值。 回想一下之前的挑战,如何 filter 数据,以及使用 map 来获取你想要的数据。 您可能需要创建其他变量,并从 getRating 函数返回平均评分。 请注意,评级在对象中是字符串,需要将其转换为数字再用于数学运算。
function getRating(watchList){
const averageRating = watchList
// 使用过滤器查找由克里斯托弗-诺兰导演的电影
.filter(film => film.Director === "Christopher Nolan")
// 使用map将其评分从字符串转换为数字
.map(film => Number(film.imdbRating))
// 使用reduce将他们的评分相加
.reduce((sumOfRatings, rating) => sumOfRatings + rating) /
watchList.filter(film => film.Director === "Christopher Nolan").length;
return averageRating;
}
console.log(getRating(watchList));
使用 map()、filter() 和 reduce() 的任何组合完成 squareList 函数的代码。 传递一个包含实数的数组给函数时,函数应返回一个新的数组,只包含正整数(小数不是整数)的平方值, 例如 [-3, 4.8, 5, 3, -3.2] 这样一个包含实数的数组。
const squareList = (arr) => {
return arr
.filter(num => num > 0 && num % parseInt(num) === 0)
.map(num => Math.pow(num, 2));
};
const squaredIntegers = squareList([-3, 4.8, 5, 3, -3.2]);
console.log(squaredIntegers);
使用 sort 方法按字母顺序给数组排序
JavaScript 的默认排序方法是 Unicode 值顺序排序,有时可能会得到意想不到的结果。 因此,建议提供一个回调函数来指定如何对数组项目排序。 这个回调函数通常叫做 compareFunction,它根据 compareFunction 的返回值决定数组元素的排序方式: 如果两个元素 a 和 b,compareFunction(a,b) 返回一个比 0 小的值,那么 a 会在 b 的前面。 如果两个元素 a 和 b,compareFunction(a,b) 返回一个比 0 大的值,那么 b 会在 a 的前面。 如果两个元素 a 和 b,compareFunction(a,b) 返回等于 0 的值,那么 a 和 b 的位置保持不变。
function alphabeticalOrder(arr) {
return arr.sort(function(a, b) {
return a === b ? 0 : a < b ? -1 : 1;
});
}
alphabeticalOrder(["a", "d", "c", "a", "z", "g"]);
在不更改原始数组的前提下返回排序后的数组
**sort 方法会产生改变原始数组中元素顺序的副作用。** 换句话说,它会改变数组的位置。 避免这种情况的一种方法是先将空数组连接到正在排序的数组上(记住 slice 和 concat 返回一个新数组),再用sort方法。
在 nonMutatingSort 函数中使用 sort 方法对数组中的元素按升序进行排列。 函数不能改变 globalArray 变量,应返回一个新数组。
var globalArray = [5, 6, 3, 2, 9];
function nonMutatingSort(arr) {
return [].concat(arr).sort(function(a, b) { return a - b; });
}
nonMutatingSort(globalArray);
使用 split 方法将字符串拆分成数组, 使用 join 方法将数组组合成字符串
split 方法将一个字符串分割成一个字符串数组。
它需要一个参数作为分隔符,它可以是用于拆分字符串或正则表达式的一个字符。 举个例子,如果分隔符是空格,你会得到一个单词数组;如果分隔符是空字符串,你会得到一个由字符串中每个字符组成的数组。
join方法用来把数组中的所有元素放入一个字符串。 并通过指定的分隔符参数进行分隔。
在 splitify 函数中用 split 方法将 str 分割成单词数组。 这个方法应该返回一个数组。 单词不一定都是用空格分隔,所以数组中不应包含标点符号。
在函数 sentensify 内用 join 方法(及其他方法)用字符串 str 中的单词造句,这个函数应返回一个字符串。 该函数应返回一个数组。 举个例子,I-like-Star-Wars 会被转换成 I like Star Wars。
function splitify(str) {
return str.split(/\W/);
}
splitify("Hello World,I-am code");
function sentensify(str) {
return str.split(/\W/).join(" ");
}
sentensify("May-the-force-be-with-you");
下面做个小练习,在开发中应用函数式编程将字符串转换为URL片段:
var globalTitle = "Winter Is Coming";
function urlSlug(title) {
return title
.split(" ")
.filter(substr => substr !== "")
.join("-")
.toLowerCase();
}
var winterComing = urlSlug(globalTitle); //"winter-is-coming"
使用 every 方法检查数组中的每个元素是否符合条件, 使用 some 方法检查数组中是否有元素是否符合条件
every 方法用于检测数组中所有元素是否都符合指定条件。 如果所有元素满足条件,返回布尔值 true,反之返回 false。
some 方法用于检测数组中任何元素是否满足指定条件。 如果有一个元素满足条件,返回布尔值 true,反之返回 false。
在 checkPositive 函数中使用 every 方法检查 arr 中是否所有元素都是正数。 函数应返回一个布尔值。
在 checkPositive 函数值中使用 some 检查 arr 中是否有元素为正数。 函数应返回一个布尔值。
function checkPositive(arr) {
return arr.every(val => val > 0);
}
checkPositive([1, 2, 3, -4, 5]);
function checkPositive(arr) {
return arr.some(elem => elem > 0);
}
checkPositive([1, 2, 3, -4, 5]);