6. JavaScript 函数的常见高级使用

81 阅读3分钟

JavaScript 函数的常见高级使用

JavaScript 有很多高级特性,这些特性使得 函数 在编程中 变得非常灵活 和 强大。理解这些特性,可以 帮助编写出 更加高效、灵活 的代码

1 闭包 (Closure)

1.1 闭包 (Closure)

闭包 是 JavaScript 函数的一个非常重要的特性,它指的是 内部函数 可以 “记住” 并 访问 定义时的作用域(离内部函数最近的外部函数的作用域),即使 外部函数 已经被调用(执行完毕)。

function outer() {
    let counter = 0;
    
    return function inner() {
        counter++;
        console.log(counter);
    };
}

const increment = outer();
increment(); // 输出 1
increment(); // 输出 2
increment(); // 输出 3
  • 在上面的代码中,inner 函数 形成了 闭包,它 访问了 outer 函数的 counter 变量;
  • 闭包使得 counter 变量不会随着 outer 函数的 执行结束 而 销毁,它被 inner 函数 保留 和 访问。
闭包的应用
  • 模拟 私有变量 和 方法
  • 事件监听器异步操作回调函数中

1.2 函数柯里化 (Currying)

它是将一个接受 多个参数 的函数,转换为 一系列 接受 一个参数 的函数 的技术。它是一个很有用的 函数式编程 技巧(实现 链式调用),尤其在某些特定的场景下能 提高代码的 可复用性、可读性 和 灵活性

在它的实现过程中,通常会利用 闭包 来保持对 参数的访问。具体来说,当我们调用 柯里化函数 时,返回 一个新的函数,这个新函数可以 访问 上一级函数的参数,直到 所有参数 都被传递完毕。

function add(a) {
  return function(b) {
    return a + b;
  };
}

const add5 = add(5); // add5 是一个接受参数 b 的函数
console.log(add5(3)); // 输出 8
  • add 函数通过 柯里化 返回了一个 新的函数,它 “记住” 了 第一个参数 a,然后等待 第二个参数 b

2 高阶函数 (Higher-Order Functions)

高阶函数是指 接受函数 作为参数,或者 返回 一个函数 的函数。

function greet(name, callback) {
    console.log(`Hello, ${name}!`);
    callback();
}

function farewell() {
    console.log("Goodbye!");
}

greet("Alice", farewell); // 输出 "Hello, Alice!" 和 "Goodbye!"
  • 这里的 greet 函数 就是一个 高阶函数,它 接受 一个函数 farewell 作为参数。
  • 高阶函数 可以用于 事件处理函数式编程 中的 链式调用、异步编程 等方面。

2.1 数组的常用方法

JavaScript 数组中的许多常用方法(如 mapfilterreduce)本质上就是 高阶函数。它们 接受一个回调函数 作为参数,允许你在数组上进行 各种操作。

  • .map():用于将 一个数组 映射成 另一个数组,回调函数中 处理 每个元素;
  • .filter():用于 根据条件 筛选 数组中的 元素,回调函数中 返回 一个布尔值
  • .reduce():用于 通过 一个回调函数 将 数组的所有元素 归纳成一个 单一的值,这个方法适用于各种 聚合操作,如 求和求最大值数组扁平化 等。
const numbers = [1, 2, 3, 4, 5];

// 使用 map 将每个元素平方
const squares = numbers.map(num => num * num);
console.log(squares); // [1, 4, 9, 16, 25]

// 使用 filter 筛选出偶数
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]

// 使用 reduce 计算数组的总和
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
reduce() 函数

基本语法:

array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue]);
  • callback:一个函数,用来执行数组中的每个元素;
    • accumulator:累计值,或者说 上次调用回调函数的 返回值。在第一次调用时,它的值是 initialValue,如果没有提供 initialValue,则为 数组的 第一个元素;
    • currentValue:当前 正在处理的 数组元素;
    • index:当前元素 在数组中的 索引(可选);
    • array:原数组(可选);
  • initialValue可选):指定 第一次调用 callbackaccumulator初始值。如果 没有提供 initialValue,则 accumulator 的初始值 是 数组的 第一个元素currentValue 则从 数组的第二个元素 开始。
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 输出 15
  • accumulator 初始化为 0(这是 initialValue);
  • currentValue 是数组中的每个元素;
  • reduce 会将 每个元素累加到 accumulator 中,最终返回 总和 15。

2.2 事件处理

在 Web 开发中,事件处理函数 通常作为 高阶函数的参数 传入。你可以通过 高阶函数 为不同事件 注册 不同的回调。

// 注册点击事件
function addClickListener(element, callback) {
  element.addEventListener('click', callback);
}

const button = document.getElementById('myButton');
addClickListener(button, function() {
  alert('Button clicked!');
});
  • 这里的 addClickListener 就是一个 高阶函数,它接受 一个事件处理函数(callback)作为参数。

2.3 函数式链式调用

函数式编程 中的 链式调用 模式,就是通过 将多个高阶函数 组合起来,逐步地 处理数据。

// 链式调用
function chain(fn) {
  return function(arg) {
    return fn(arg);
  };
}

const add = x => x + 1;
const multiply = x => x * 2;

const process = chain(add)(2);  // 2 + 1 = 3
console.log(process); // 3
  • 上面的例子通过 高阶函数 chain 来实现 函数链式调用,使得 函数的组合 和 复用 变得更加方便。

2.4 异步编程

在异步编程中,尤其是与 I/O 操作定时器HTTP 请求 相关的代码中,高阶函数 是非常常见的。

例如,setTimeoutsetInterval 等函数 都是 高阶函数,它们 接受一个回调函数 作为 参数,用于 在指定的时间间隔后 执行某个操作。

setTimeout(function() {
  console.log('This is executed after 2 seconds');
}, 2000);

2.5 表单验证和数据处理

高阶函数 也可以用于 表单验证 和 数据处理。通过 将验证规则 封装成 函数,你可以 为表单输入 提供 通用的验证逻辑。

function validate(validator) {
  return function(value) {
    return validator(value);
  };
}

const isNotEmpty = value => value.trim() !== '';
const isEmail = value => /\S+@\S+\.\S+/.test(value);

const validateNotEmpty = validate(isNotEmpty);
const validateEmail = validate(isEmail);

console.log(validateNotEmpty('Hello')); // true
console.log(validateEmail('user@example.com')); // true
  • 在这个例子中,validate 是一个 高阶函数,它接受 一个验证函数 作为参数,并返回 一个新的 验证函数。

3 函数参数(Function Parameters)

3.1 默认参数 (Default Parameters)

默认参数是 ES6 新增的特性,可以为函数参数 指定 默认值。如果调用时 没有传入该参数,则使用默认值。

function greet(name = "Guest") {
    console.log(`Hello, ${name}!`);
}

greet("Alice");  // 输出 Hello, Alice!
greet();         // 输出 Hello, Guest!

3.2 剩余参数 (Rest Parameters)

剩余参数 允许我们 将不确定数量的参数 表示为 一个数组。它使用 ... 语法,可以 在函数中 捕获 所有传递的 额外参数。

function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3)); // 输出 6
console.log(sum(5, 10, 15, 20)); // 输出 50
  • ...numbers 捕获 所有传递给 sum 函数的 参数,并将它们 放入一个数组中。

3.3 函数参数解构 (Destructuring Parameters)

函数的参数 可以进行 解构赋值,直接从传入的 对象数组 中 提取值。

示例(对象结构)

function greet({ name, age }) {
    console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}

const person = { name: "Alice", age: 25 };
greet(person);  // 输出 Hello, my name is Alice and I am 25 years old.

示例(数组结构)

function sum([a, b, c]) {
    return a + b + c;
}

console.log(sum([1, 2, 3])); // 输出 6

4 函数的 this 绑定 (Binding this)

JavaScript 函数的 this 关键字 的行为 可能会因 调用方式不同 而 有所不同。

通过使用 callapplybind 方法,我们可以 手动指定 this 的值。

示例 callapply

callapply 允许我们将 一个对象上下文this)传递给 一个函数。

callapply 唯一的区别是 传递参数 的 方式:call逐个参数 传递,apply 传递 一个数组

function greet() {
    console.log(`Hello, my name is ${this.name}`);
}

const person = { name: "Alice" };
greet.call(person); // 输出 Hello, my name is Alice
greet.apply(person); // 输出 Hello, my name is Alice

示例 bind

bind 返回 一个 新的函数固定this 的值,且 不会立即执行

function greet() {
    console.log(`Hello, my name is ${this.name}`);
}

const person = { name: "Alice" };
const greetPerson = greet.bind(person);
greetPerson();  // 输出 Hello, my name is Alice

5 异步函数与回调 (Async Functions and Callbacks)

ES6 引入了 asyncawait,它们使得处理 异步操作 更像 同步代码函数 async 声明会 返回一个 Promise,而 await暂停 函数的 执行,直到 Promise 被解决。

async function fetchData() {
    let result = await fetch("https://api.example.com/data");
    let data = await result.json();
    console.log(data);
}

fetchData();

async 函数 允许我们 使用 await等待 异步操作 的结果,减少 回调地狱