JavaScript函数式编程[入门]

简介: JavaScript函数式编程[入门]




⭐️函数式编程

函数式编程是另一种软件开发方法。在函数式编程中,代码被组织成较小的基本的函数,可以结合起来构建复杂的程序。

在这个课程中,你将学习函数式编程的核心概念,包括纯函数、如何避免突变、如何使用 .map() 和 .filter() 等方法编写更整洁的代码

⭐️学习函数式编程

函数式编程是一种方案简单、功能独立、对作用域外没有任何副作用的编程范式:INPUT -> PROCESS -> OUTPUT。

函数式编程:

1)功能独立——不依赖于程序的状态(比如可能发生变化的全局变量);

2)纯函数——同一个输入永远能得到同一个输出

3)有限的副作用——可以严格地限制函数外部对状态的更改

freeCodeCamp 的成员们爱喝茶。

在代码编辑器中,已经为你定义好了prepareTea和getTea函数。 调用 getTea 函数为团队准备 40 杯茶,并将它们存储在 tea4TeamFCC 变量里。

// 函数返回表示“一杯绿茶(green tea)”的字符串
const prepareTea = () => 'greenTea';
/*
有一个函数(代表茶的种类)和需要几杯茶,下面的函数返回一个数组,包含字符串(每个字符串表示一杯特别种类的茶)。
*/
const getTea = (numOfCups) => {
  const teaCups = [];
  for(let cups = 1; cups <= numOfCups; cups += 1) {
    const teaCup = prepareTea();
    teaCups.push(teaCup);
  }
  return teaCups;
};
// 只修改这一行下面的代码
const tea4TeamFCC = getTea(40);
// 只修改这一行上面的代码

了解函数式编程术语

FCC 团队需求有变更,现在想要两种茶:绿茶(green tea)和红茶(black tea)。 事实证明,用户需求变更是很常见的。

基于以上信息,我们需要重构上一节挑战中的 getTea 函数来处理多种茶的请求。 我们可以修改 getTea 接受一个函数作为参数,使它能够修改茶的类型。 这让 getTea 更灵活,也使需求变更时为程序员提供更多控制权。

首先,我们将介绍一些术语:

Callbacks 是被传递到另一个函数中调用的函数。 你应该已经在其他函数中看过这个写法,例如在 filter 中,回调函数告诉 JavaScript 以什么规则过滤数组。

函数就像其他正常值一样,可以赋值给变量、传递给另一个函数,或从其它函数返回这种函数叫做头等 first class 函数。 在 JavaScript 中,所有函数都是头等函数

将函数作为参数或将函数作为返回值返回的函数叫作高阶函数。

当函数被传递给另一个函数或从另一个函数返回时,那些传入或返回的函数可以叫作 lambda

准备 27 杯绿茶和 13 杯红茶,分别存入 tea4GreenTeamFCC 和 tea4BlackTeamFCC 变量。 请注意,getTea 函数已经变了,现在它接收一个函数作为第一个参数。

注意:数据(茶的数量)作为最后一个参数。 我们将在后面的课程中对此进行更多讨论。

// 函数返回表示“一杯绿茶(green tea)”的字符串
const prepareGreenTea = () => 'greenTea';
// 函数返回表示“一杯红茶(black tea)”的字符串
const prepareBlackTea = () => 'blackTea';
/*
有一个函数(代表茶的种类)和需要几杯茶,下面的函数返回一个数组,包含字符串(每个字符串表示一杯特别种类的茶)。
*/
const getTea = (prepareTea, numOfCups) => {
  const teaCups = [];
  for(let cups = 1; cups <= numOfCups; cups += 1) {
    const teaCup = prepareTea();
    teaCups.push(teaCup);
  }
  return teaCups;
};
// 只修改这一行下面的代码
const tea4GreenTeamFCC = getTea(prepareGreenTea,27);
const tea4BlackTeamFCC = getTea(prepareBlackTea,13);
// 只修改这一行上面的代码
console.log(
  tea4GreenTeamFCC,
  tea4BlackTeamFCC
);

了解使用命令式编程的危害

使用函数式编程是一个好的习惯。 它使你的代码易于管理,避免潜在的 bug。 但在开始之前,先看看命令式编程方法,以强调你可能有什么问题

在英语 (以及许多其他语言) 中,命令式时态用来发出指令。 同样,命令式编程是向计算机提供一套执行任务的声明

命令式编程常常改变程序状态,例如更新全局变量。 一个典型的例子是编写 for 循环,它为一个数组的索引提供了准确的迭代方向。

相反,函数式编程是声明式编程的一种形式。 通过调用方法或函数来告诉计算机要做什么

JavaScript 提供了许多处理常见任务的方法,所以你无需写出计算机应如何执行它们。 例如,你可以用 map 函数替代上面提到的 for 循环来处理数组迭代。 这有助于避免语义错误,如调试章节介绍的 “Off By One Errors”。

考虑这样的场景:你正在浏览器中浏览网页,并想操作你打开的标签。 下面我们来试试用面向对象的思路来描述这种情景。

窗口对象由选项卡组成,通常会打开多个窗口。 窗口对象中每个打开网站的标题都保存在一个数组中。 在对浏览器进行了如打开新标签、合并窗口、关闭标签之类的操作后,你需要输出所有打开的标签。 关掉的标签将从数组中删除,新打开的标签(为简单起见)则添加到数组的末尾

代码编辑器中显示了此功能的实现,其中包含 tabOpen(),tabClose(),和 join() 函数。 tabs 数组是窗口对象的一部分用于储存打开页面的名称。

在编辑器中运行代码。 它使用了有副作用的方法,导致输出错误。 存储在 finalTabs.tabs 中的打开标签的最终列表应该是 ['FB', 'Gitter', 'Reddit', 'Twitter', 'Medium', 'new tab', 'Netflix', 'YouTube', 'Vine', 'GMail', 'Work mail', 'Docs', 'freeCodeCamp', 'new tab'],但输出会略有不同。

修改 Window.prototype.tabClose 使其删除正确的标签。

// tabs 是在窗口中打开的每个站点的 title 的数组
const Window = function(tabs) {
  this.tabs = tabs; // 我们记录对象内部的数组
};
// 当你将两个窗口合并为一个窗口时
Window.prototype.join = function(otherWindow) {
  this.tabs = this.tabs.concat(otherWindow.tabs);
  return this;
};
// 当你在最后打开一个选项卡时
Window.prototype.tabOpen = function(tab) {
  this.tabs.push('new tab'); // 我们现在打开一个新的选项卡
  return this;
};
// 当你关闭一个选项卡时
Window.prototype.tabClose = function(index) {
  // 只修改这一行下面的代码
  const tabsBeforeIndex = this.tabs.splice(0, index); // 点击之前获取 tabs
  const tabsAfterIndex = this.tabs.splice(1); // 点击之后获取 tabs
  this.tabs = tabsBeforeIndex.concat(tabsAfterIndex); // 将它们合并起来
  // 只修改这一行上面的代码
  return this;
 };
// 我们创建三个浏览器窗口
const workWindow = new Window(['GMail', 'Inbox', 'Work mail', 'Docs', 'freeCodeCamp']); // 你的邮箱、Google Drive 和其他工作地点
const socialWindow = new Window(['FB', 'Gitter', 'Reddit', 'Twitter', 'Medium']); // 社交网站
const videoWindow = new Window(['Netflix', 'YouTube', 'Vimeo', 'Vine']); // 娱乐网站
// 现在执行打开选项卡,关闭选项卡和其他操作
const finalTabs = socialWindow
  .tabOpen() // 打开一个新的选项卡,显示猫的图片
  .join(videoWindow.tabClose(2)) // 关闭视频窗口的第三个选项卡,并合并
  .join(workWindow.tabClose(1).tabOpen());
console.log(finalTabs.tabs);
const tabsBeforeIndex = this.tabs.slice(0, index); // get the tabs before the tab
 const tabsAfterIndex = this.tabs.slice(index + 1); // get the tabs after the tab

splice和slice区别

Splice ()应该始终谨慎使用,因为它修改正在处理的内容。有关文档以及拼接和切片之间的差异,请参阅:

1.splice()方法改变了原始的数组,它可以删除或者替换数组中的元素,并且能够在指定位置插入新的元素。这个方法会修改原始数组并返回被删除或被替换元素组成的一个新数组。

const months = ['Jan', 'March', 'April', 'June'];
//新增元素
let arr = months.splice(1, 0, 'Feb'); // 在索引为 1 的位置插入'Feb'
console.log(arr);//[]新增元素返回的是一个空数组
console.log(months); // ["Jan", "Feb", "March", "April", "June"]
//替换元素
let newArr = months.splice(4, 1, 'May'); // 替换索引为 4 的元素为'May'
console.log(newArr);//['June'] 返回被替换的元素组成的新数组
console.log(months); // ['Jan', 'Feb', 'March', 'April', 'May']
//删除元素
let val = months.splice(1, 1)//指定在下标为1的位置删除一个元素
console.log(val);//['Feb'] 返回被删除的元素组成的新数组
console.log(months);//['Jan', 'March', 'April', 'May']//改变后的原数组

因此,splice()和slice()两个方法在用途上是有较大差别的。splice()主要用于修改原始数组,而slice()则用于从原始数组中提取需要的元素。

使用函数式编程避免变化和副作用

如果你还没想通,上一个挑战的问题出在 tabClose() 函数里的 splice。 不幸的是,splice 修改了调用它的原始数组,所以第二次调用它时是基于修改后的数组,才给出了意料之外的结果

这是一个小例子,还有更广义的定义——在变量,数组或对象上调用一个函数,这个函数会改变对象中的变量或其他东西。

函数式编程的核心原则之一是不改变任何东西。 变化会导致错误。 如果一个函数不改变传入的参数、全局变量等数据,那么它造成问题的可能性就会小很多。

前面的例子没有任何复杂的操作,但是 splice 方法改变了原始数组,导致 bug 产生。

回想一下,在函数式编程中,改变或变更叫做 mutation,这种改变的结果叫做“副作用”(side effect)。 理想情况下,函数应该是不会产生任何副作用的 pure function。

让我们尝试掌握这个原则:不要改变代码中的任何变量或对象

填写 incrementer 函数的代码,使其返回值为全局变量 fixedValue 增加 1 。

// 全局变量
let fixedValue = 4;
function incrementer() {
  // 只修改这一行下面的代码
  return 1+fixedValue
  // 只修改这一行上面的代码
}

传递参数以避免函数中的外部依赖

上一个挑战是更接近函数式编程原则的挑战,但是仍然缺少一些东西。

虽然我们没有改变全局变量值,但在没有全局变量 fixedValue 的情况下,incrementer 函数将不起作用

函数式编程的另一个原则是:总是显式声明依赖关系。 如果函数依赖于一个变量或对象,那么将该变量或对象作为参数直接传递到函数中。

这样做会有很多好处。 其中一点是让函数更容易测试,因为你确切地知道参数是什么,并且这个参数也不依赖于程序中的任何其他内容

其次,这样做可以让你更加自信地更改,删除或添加新代码。 因为你很清楚哪些是可以改的,哪些是不可以改的,这样你就知道哪里可能会有潜在的陷阱。

最后,无论代码的哪一部分执行它,函数总是会为同一组输入生成相同的输出

更新 incrementer 函数,明确声明其依赖项。

编写 incrementer 函数,获取它的参数,然后将值增加 1。

// 全局变量
let fixedValue = 4;
// 只修改这一行下面的代码
function incrementer(fixedValue) {
  return fixedValue+1
  // 只修改这一行上面的代码
}

在函数中重构全局变量

目前为止,我们已经看到了函数式编程的两个原则:

不要更改变量或对象 - 创建新变量和对象,并在需要时从函数返回它们。 提示:使用类似 const newArr = arrVar 的东西,其中 arrVar 是一个数组,只会创建对现有变量的引用,而不是副本。 所以更改 newArr 中的值会同时更改 arrVar 中的值

声明函数参数 - 函数内的任何计算仅取决于参数,而不取决于任何全局对象或变量

给数字增加 1 不够有意思,但是我们可以在处理数组或更复杂的对象时应用这些原则。

重构代码,使全局数组 bookList 在函数内部不会被改变。 add 函数可以将指定的 bookName 增加到数组末尾并返回一个新的数组(列表)。 remove 函数可以从数组中移除指定 bookName。

注意: 两个函数都应该返回一个数组,任何新参数都应该在 bookName 参数之前添加。

// 全局变量
const bookList = ["The Hound of the Baskervilles", "On The Electrodynamics of Moving Bodies", "Philosophiæ Naturalis Principia Mathematica", "Disquisitiones Arithmeticae"];
// 修改这行下面的代码
function add(arr,bookName) {
  //let newarr=[...arr]
  return arr.slice().concat(bookName);
  // 修改这行上面的代码
}
// 修改这行下面的代码
function remove(arr,bookName) {
  const book_index = bookList.indexOf(bookName);
  if (book_index >= 0) {
    return arr.slice(0,book_index).concat(arr.slice(book_index+1));
    // 修改这行上面的代码
    }
}
// 全局变量
const bookList = ["The Hound of the Baskervilles", "On The Electrodynamics of Moving Bodies", "Philosophiæ Naturalis Principia Mathematica", "Disquisitiones Arithmeticae"];
// 修改这行下面的代码
function add(arr,bookName) {
  let newarr=[...arr]
  newarr.push(bookName);
  return newarr
  // 修改这行上面的代码
}
// 修改这行下面的代码
function remove(arr,bookName) {
  let newarr=[...arr]
  const book_index = bookList.indexOf(bookName);
  if (book_index >= 0) {
    newarr.splice(book_index,1)
    return newarr
    // 修改这行上面的代码
    }
}
// 全局变量
const bookList = ["The Hound of the Baskervilles", "On The Electrodynamics of Moving Bodies", "Philosophiæ Naturalis Principia Mathematica", "Disquisitiones Arithmeticae"];
// 修改这行下面的代码
function add(arr,bookName) {
  return [...arr,bookName]
  // 修改这行上面的代码
}
// 修改这行下面的代码
function remove(arr,bookName) {
  return arr.filter((book)=>book!=bookName)
}

使用 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(mov=>({
  title:mov.Title,
  rating:mov.imdbRating
  })
  )
// 只修改这一行上面的代码
console.log(JSON.stringify(ratings));

在原型上实现 map 方法

之前用到了 Array.prototype.map() 方法(即 map()),通过 map 返回一个与调用它的数组长度相同的数组只要它的回调函数不改变原始数组,它就不会改变原始数组。

换句话说,map 是一个纯函数,它的输出仅取决于输入的数组和作为参数传入的回调函数。 此外,它接收另一个函数作为它的参数

实现一个 map,加深对它的了解。 你可以用 for 循环或者 Array.prototype.forEach() 方法。

写一个和 Array.prototype.map() 一样的 Array.prototype.myMap()。 不能使用内置的 map 方法。 在 myMap 方法内,可以使用 this 访问 Array 实例。

Array.prototype.myMap = function(callback) {
  const newArray = [];
  // 只修改这一行下面的代码
  for(let i=0; i<this.length;i++){
    newArray.push(callback(this[i],i,this))
  }
  // 只修改这一行上面的代码
  return newArray;
};

使用 filter 方法从数组中提取数据

另一个有用的数组方法是 filter()(即 Array.prototype.filter())。

filter 在一个数组的每个元素上调用一个函数,并返回一个新的数组,其中只包含该函数返回一个真值的元素,也就是说,一个被传递给 Boolean() 构造函数后返回 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 值在对象中保存为字符串,你可能需要将它转换成数字来执行运算。

// 全局变量
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 filteredList = watchList
.filter(item=>item.imdbRating>=8.0)
.map(item=>({title:item.Title,rating:item.imdbRating}))
// 只修改这一行上面的代码
console.log(filteredList);
// 全局变量
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 filteredList = watchList
.map(item=>({title:item.Title,rating:item.imdbRating}))
.filter(item=>item.rating>=8.0)
// 只修改这一行上面的代码
console.log(filteredList);

在原型上实现 filter 方法

为了加深对 filter 的理解,可以自己实现一个。 可以用 for 循环或 Array.prototype.forEach()

编写一个和 Array.prototype.filter() 功能一样的 Array.prototype.myFilter() 方法。 不能使用内置的 filter 方法。 在 myFilter 方法内部,可以使用 this 访问 Array 实例。

Array.prototype.myFilter = function(callback) {
  const newArray = [];
  // 只修改这一行下面的代码
  for(let i=0; i<this.length;i++){
    if(callback(this[i],i,this)){
      newArray.push(this[i])
    }
  }
  // 只修改这一行上面的代码
  return newArray;
};

使用 slice 方法返回数组的一部分

slice 方法可以从已有数组中返回指定元素。 它接受两个参数,第一个规定从何处开始选取,第二个规定从何处结束选取(不包括该元素)。 如果没有传参,则默认为从数组的开头开始到结尾结束,这是复制整个数组的简单方式。 slice 返回一个新数组,不会修改原始数组。

举个例子:

const arr = ["Cat", "Dog", "Tiger", "Zebra"];
const newArray = arr.slice(1, 3);
newArray 值为 ["Dog", "Tiger"]

在 sliceArray 函数中使用 slice 方法,给出 beginSlice 和 endSlice 索引,返回 anim 数组的一部分。 这个函数应返回一个数组。

function sliceArray(anim, beginSlice, endSlice) {
  // 只修改这一行下面的代码
return anim.slice(beginSlice, endSlice)
  // 只修改这一行上面的代码
}
const inputAnim = ["Cat", "Dog", "Tiger", "Zebra", "Ant"];
sliceArray(inputAnim, 1, 3);

使用 slice 而不是 splice 从数组中移除元素

使用数组时经常遇到要删除一些元素并保留数组剩余部分的情况。 为此,JavaScript 提供了 splice 方法,它接收两个参数:从哪里开始删除项目的索引,和要删除的项目数。 如果没有提供第二个参数,默认情况下是移除一直到结尾的所有元素。 但 splice 方法会改变调用它的原始数组。 举个例子:

const cities = ["Chicago", "Delhi", "Islamabad", "London", "Berlin"];
cities.splice(3, 1);

在这里 splice 返回字符串 London 并从城市数组中删除它。 cities 将有值 [“Chicago”, “Delhi”, “Islamabad”, “Berlin”]。

正如我们在上一次挑战中看到的那样,slice 方法不会改变原始数组,而是返回一个可以保存到变量中的新数组。 回想一下,slice 方法接收两个参数,从开始索引开始选取到结束(不包括该元素),并在新数组中返回这些元素。 使用 slice 方法替代 splice 有助于避免数组变化产生的副作用

用 slice 代替 splice 重写 nonMutatingSplice 函数。 将 cities 数组长度限制为 3,并返回一个仅包含前 3 项的新数组。

不要改变提供给函数的原始数组。

function nonMutatingSplice(cities) {
  return cities.slice(0,3);
}

使用 concat 方法组合两个数组

Concatenation 意思是将元素连接到尾部。 同理,JavaScript 为字符串和数组提供了concat方法。 对数组来说,在一个数组上调用 concat 方法,然后提供另一个数组作为参数添加到第一个数组末尾。 它返回一个新数组,不会改变任何一个原始数组。 举个例子:

[1, 2, 3].concat([4, 5, 6]);

返回的数组将是 [1, 2, 3, 4, 5, 6]。

在 nonMutatingConcat 函数里使用 concat,将 attach 拼接到 original 尾部。 函数返回拼接后的数组。

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 将一个元素添加到调用它的数组的末尾,这样会改变该数组。 举个例子:

const arr = [1, 2, 3];
arr.push(4, 5, 6);

arr 的值被修改为 [1, 2, 3, 4, 5, 6],这不是函数编程方式。

concat 方法可以将新项目添加到数组末尾,而不附带改变数组

修改 nonMutatingPush 函数,用 concat 将 newItem 添加到 original 末尾,而不改变 original 或 newItem 数组。 该函数应返回一个数组。

function nonMutatingPush(original, newItem) {
  // 只修改这一行下面的代码
  return original.concat(newItem);
  // 只修改这一行上面的代码
}
const first = [1, 2, 3];
const second = [4, 5];
nonMutatingPush(first, second);

相关文章
|
1月前
|
JavaScript 前端开发 C语言
javascript基础入门
javascript基础入门
24 1
|
3月前
|
JSON JavaScript 前端开发
Danfo.js专题 - Danfo.js与Dnotebook简介与入门
Danfo.js专题 - Danfo.js与Dnotebook简介与入门
47 0
|
3月前
|
JSON JavaScript 前端开发
Webpack【Webpack图片处理、Webpack中proxy代理 、自动清理dist、Webpack优化、JavaScript中的代码检查】(三)-全面详解(学习总结---从入门到深化)(下)
Webpack【Webpack图片处理、Webpack中proxy代理 、自动清理dist、Webpack优化、JavaScript中的代码检查】(三)-全面详解(学习总结---从入门到深化)
50 2
|
2月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
27 0
|
3月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)(上)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
29 0
|
2月前
|
消息中间件 Web App开发 JavaScript
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
77 0
|
3月前
|
JavaScript 前端开发 API
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)(下)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
35 0
|
3月前
|
消息中间件 Web App开发 JavaScript
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)(上)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
42 0
|
3月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)(下)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
26 0
|
2月前
|
前端开发 JavaScript
从零开始学习前端开发:HTML、CSS、JavaScript入门指南
【2月更文挑战第1天】本文将带领读者从零开始学习前端开发,介绍HTML、CSS和JavaScript的基础知识与应用,帮助读者快速入门前端开发领域。
65 1