JavaScript 高阶函数介绍

686 阅读4分钟

如果你正在学习或使用 JavaScript, 那么一定遇到过高阶函数这个术语。听起来很高级复杂,其实不然。

First-Class Functions

JavaScript 将函数作为一等公民。这是因为函数是对象。

在 JavaScript 中,函数是一种特殊类型的对象。它们是 Function 对象。

function greeting() {
  console.log('Hello World');
}

greeting();	// 'Hello World'

为了证明函数是对象,我们可以这样:

greeting.lang = 'English';

console.log(greeting.lang);		// 'English'

Note——虽然这种写法在 JavaScript 种是完全有效的,但被认为是有害的。不应该向函数对象添加随机属性,如果需要,请使用对象。

在 JavaScript 中,你可以将它们作为参数传递给其他函数(回调),将它们分配给变量并传递给其他函数等等。这也是函数被称为一等公民的原因。

将函数赋值给变量

在 JavaScript 中,我们可以将函数赋值给变量。

const square = function(x) {
  return x * x;
}

square(5);

将函数作为参数传递

我们可以将函数作为参数传递给其他函数。

function formalGreeting() {
  console.log("How are you?");
}

function casualGreeting() {
  console.log("What's up?")
}

function greet(type, greetFormal, greetCasual) {
  if(type === 'formal') {
    greetFormal();
  } else if(type === 'casual') {
    greetCasual();
  }
}

// prints 'What's up?'
greet('casual', formalGreeting, casualGreeting);

现在我们已经知道一等公民是什么,让我们深入理解 JavaScript 中的高阶函数。

高阶函数

高阶函数是对其他函数进行操作的函数,可以将它们作为参数或返回它们。 简单来说,高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回。

例如,Array.prototype.map,Array.prototype.filter 和 Array.prototype.reduce 是 JavaScript 中内置的一些高阶函数。

简单介绍下这些内置高阶函数,并与不使用高阶函数的方案作下对比。

Array.prototype.map

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

语法

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
 // Return element for new_array 
}[, thisArg])

传递给 map() 的回调函数接受三个参数,分别是 currentValue, index(可选), array(可选),除了 callback 外,还可以接受 this(可选)值,作为执行 callback 函数是使用的 this 值。

例子

给定一个数字数组,想要创建一个新数组,其每个元素是数组的两倍。

不使用高阶函数 map
const arr1 = [1, 2, 3];
const arr2 = [];
for(let i = 0; i < arr1.length; i++) {
  arr2.push(arr1[i] * 2);
}
// prints [ 2, 4, 6 ]
console.log(arr2);
使用高阶函数 map
const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item * 2);
console.log(arr2);

Array.prototype.filter

filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素

语法

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

传递给 filter() 的回调函数接受的参数和 map 相同。

例子

给定一个包含姓名和年龄属性的对象的数组,创建一个只包含年龄大于18的数组。

不使用高阶函数 filter
const persons = [
  { name: 'Peter', age: 16 },
  { name: 'Mark', age: 18 },
  { name: 'John', age: 27 },
  { name: 'Jane', age: 14 },
  { name: 'Tony', age: 24},
];
const fullAge = [];
for(let i = 0; i < persons.length; i++) {
  if(persons[i].age >= 18) {
    fullAge.push(persons[i]);
  }
}
console.log(fullAge);
使用高阶函数 filter
const persons = [
  { name: 'Peter', age: 16 },
  { name: 'Mark', age: 18 },
  { name: 'John', age: 27 },
  { name: 'Jane', age: 14 },
  { name: 'Tony', age: 24},
];
const fullAge = persons.filter(person => person.age >= 18);
console.log(fullAge);

Array.prototype.reduce

reduce() 方法对数组中的每个元素执行一个提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。

语法

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

reducer 函数接收4个参数:

  1. Accumulator (acc) (累计器)
  2. Current Value (cur) (当前值)
  3. Current Index (idx) (当前索引)
  4. Source Array (src) (源数组)

reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

如果有 initialValue, accumulator 等于 initialValue,currentValue 等于数组第一个元素。

如果没有有 initialValue, accumulator 等于 数组第一个元素,currentValue 等于数组第二个元素。

例子

求给定数组的和

不使用高阶函数 reduce
const arr = [5, 7, 1, 8, 4];
let sum = 0;
for(let i = 0; i < arr.length; i++) {
  sum = sum + arr[i];
}
// prints 25
console.log(sum);
使用高阶函数 reduce
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
  return accumulator + currentValue;
});
// prints 25
console.log(sum);

创建自己的高阶函数

到目前为止,我们介绍了 JavaScript 中内置的各种高阶函数。现在让我们创建自己的高阶函数。

假设 JavaScript 没有原生 map 方法。 我们可以自己构建它。

假设有一个字符串数组,希望将此数组转换为整数数组,其中每个元素表示原始数组中字符串的长度。

function mapForEach(arr, fn) {
  const newArray = [];
  for(let i = 0; i < arr.length; i++) {
    newArray.push(
      fn(arr[i])
    );
  }
  return newArray;
}
const lenArray = mapForEach(strArray, function(item) {
  return item.length;
});
// prints [ 10, 6, 3, 4, 1 ]
console.log(lenArray);

总结

我们已经了解了高阶函数和一些内置的高阶函数。 我们还学习了如何创建自己的高阶函数。

简而言之,高阶函数是一个函数,可以接收函数作为参数,甚至可以返回一个函数。

参考资料