js函数(function )全方位解析

285 阅读7分钟

JavaScript 中的函数是编程的核心概念之一,涉及分类、参数的使用以及高级函数的概念。以下是对这些内容的全方位解析。

1. 函数的分类

在 JavaScript 中,函数可以根据不同的标准进行分类:

1.1 按照定义方式
  • 函数声明

    function greet() {
      console.log('Hello!');
    }
    
  • 函数表达式

    const greet = function() {
      console.log('Hello!');
    };
    
  • 箭头函数(ES6):

    const greet = () => {
      console.log('Hello!');
    };
    
1.2 按照用途
  • 普通函数:用于执行特定的任务。
  • 回调函数:作为参数传递给其他函数,在特定事件发生时被调用。
  • 构造函数:通过 new 关键字创建对象的函数,通常以大写字母开头。
  • 生成器函数:使用 function* 声明,可以在执行过程中暂停与恢复。

2. 函数的参数

在 JavaScript 中,函数的参数非常灵活,可以使用多种方式处理:

2.1 普通参数
function add(a, b) {
  return a + b;
}
2.2 默认参数(ES6)
function multiply(a, b = 1) {
  return a * b;
}
2.3 剩余参数(Rest Parameters)

使用 ... 语法,可以将未定义的参数组合成数组。

function sum(...args) {
  return args.reduce((total, num) => total + num, 0);
}
2.4 结构赋值

可以在函数参数中直接对对象或数组进行结构赋值。

function display({ name, age }) {
  console.log(`Name: ${name}, Age: ${age}`);
}

display({ name: 'Alice', age: 25 });

3. 高级函数

高级函数是指能够操作其他函数的函数。主要包括:

3.1 函数作为参数

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

function executeCallback(callback) {
  callback();
}

executeCallback(() => console.log('Callback executed!'));
3.2 函数作为返回值

一个函数可以返回另一个函数。

function outerFunction() {
  return function innerFunction() {
    console.log('Inner function executed!');
  };
}

const inner = outerFunction();
inner();
3.3 闭包

闭包是访问自由变量的函数,能够保持其外部函数的作用域。

function makeCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

4. 高级概念

4.1 记忆化(Memoization)

记忆化是一种优化技术,用于缓存函数的返回值,以提高性能。

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

const fibonacci = memoize(n => {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});
4.2 高阶函数

高阶函数是指接受函数作为参数或返回函数的函数。它们可以实现函数组合、函数回调等功能。

function higherOrderFunction(fn) {
  return function(arg) {
    console.log('Before calling the function');
    fn(arg);
    console.log('After calling the function');
  };
}

const wrappedFunc = higherOrderFunction(console.log);
wrappedFunc('Hello, World!');

4.3. 纯函数

定义

纯函数是一种没有副作用的函数,即:

  • 对相同的输入,始终返回相同的输出。
  • 不会依赖或修改外部状态,即不修改外部变量或数据。
特点
  • 可预测性:由于输出仅依赖输入,测试和调试相对简单。
  • 易于优化:纯函数容易进行某些优化(如缓存、并行处理)。
示例
function add(a, b) {
  return a + b; // 纯函数
}

let x = 5;
function increment() {
  x++; // 非纯函数,因修改了外部变量
  return x;
}

4.4. 函数提升

定义

函数提升是指 JavaScript 在执行代码之前,会把函数声明提升到作用域顶部。不同于函数表达式,函数声明会在代码运行之前被处理。

特点
  • 提升适用于函数声明,但不适用于函数表达式。
  • 提升的顺序由代码块的顺序决定。
示例
console.log(greet()); // "Hello"
function greet() {
  return 'Hello';
}

// console.log(sayHi()); // TypeError: sayHi is not a function
var sayHi = function() {
  return 'Hi';
};

4.5. 使用剩余参数注意的问题

剩余参数(Rest Parameters)

使用 ... 操作符收集函数的剩余参数,形成一个数组。

注意事项
  • 剩余参数必须是函数参数列表中的最后一个参数。
  • 不能与其他参数混用,如命名参数后面再使用剩余参数。
示例
function sum(...args) {
  return args.reduce((acc, curr) => acc + curr, 0);
}

// console.log(sum(1, 2, 3)); // 6

4.6. 函数调用时应注意的问题

传参方式
  • 确保传入的参数类型与函数预期相符。可以通过检查参数类型来增强健壮性。
function multiply(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error('Both arguments must be numbers.');
  }
  return a * b;
}
this 绑定
  • 注意在不同上下文中 this 的绑定。常见的问题是使用常规函数和箭头函数时 this 的行为不同。
const obj = {
  value: 42,
  getValue: function() {
    return this.value; // this 指向 obj
  },
  getArrowValue: () => this.value // this 不指向 obj
};
处理异常
  • 在进行函数调用时,可以使用 try...catch 块来处理可能的异常,保持应用的健壮性。
try {
  console.log(multiply(2, 'string')); // Error
} catch (error) {
  console.error(error.message); // "Both arguments must be numbers."
}
递归调用
  • 注意递归调用的基准条件,避免导致栈溢出。
function factorial(n) {
  if (n <= 1) return 1; // 基准条件
  return n * factorial(n - 1);
}

5. 普通函数和递归函数的异同

相同点
  1. 函数结构:二者都是函数,都可以接受参数并返回结果。
  2. 使用范围:可以在相同的场景中使用,都是编程解决问题的工具。
  3. 暴露接口:都可以作为模块或类的方法被外部调用。
不同点
特点普通函数递归函数
自我调用不调用自身直接调用自身
解决方法通常采用迭代或顺序处理通过分解问题为子问题进行处理
基准条件不需要基准条件需要基准条件以终止递归
复杂性相对简单,易于理解可能比较复杂,容易出现栈溢出的问题
性能表现通常性能较稳定可能受限于调用栈深度,且可能存在性能损耗(尤其是深层递归时)

6. 普通函数和箭头函数的异同

相同点
  1. 函数结构:两者都是函数,可以接受参数并返回结果,能用于定义回调。
  2. 可以作为一等公民:普通函数和箭头函数都可以作为参数传递和返回值。
不同点
特点普通函数箭头函数
 this 绑定动态绑定,取决于调用上下文静态绑定,继承外部作用域的this 
能否作为构造函数可以使用 new 关键字不能使用 new 关键字
 arguments 对象有自己的 arguments 对象不支持 arguments 对象
函数提升声明会被提升,可以在声明之前调用不会被提升,须在定义后调用
表达式简化需要完整的语法形式可以省略大括号及 return 语句
使用场景
  • 普通函数:适合需要自己管理 this 绑定的场景、作为构造函数或者需要使用 arguments 对象的情况。
  • 箭头函数:适合需要保持上下文 this 的场景,常用于回调函数、数组方法(如 mapfilterreduce)等,可以使代码更加简洁和可读。

7. 函数表达式和函数声明的区别

一、函数声明
定义

函数声明是使用 function 关键字声明的函数。

语法
function functionName(parameters) {
  // 函数体
}
特点
  1. 提升(Hoisting) :函数声明会被提升到其所在作用域的顶部。这意味着可以在函数声明之前调用该函数。
  2. 命名函数:函数声明是命名的,可以在函数体内部使用 arguments 对象。
  3. 定义形式:函数声明具有明确的定义形式。
示例
// 函数声明
function greet() {
  console.log("Hello!");
}

greet(); // 可以在声明之前调用,输出 "Hello!"

二、函数表达式

定义

函数表达式是将一个函数赋值给变量或属性的方式。

语法
const functionName = function(parameters) {
  // 函数体
};
特点
  1. 无提升:函数表达式不会被提升,必须在定义后才能调用,否则会抛出错误。
  2. 可匿名:函数表达式可以是匿名的,可以没有名称,通常用于立即调用或作为回调。
  3. 可以作为对象的方法:函数表达式可以被赋值为对象的属性。
示例
// 函数表达式
const greet = function() {
  console.log("Hello!");
};

// greet(); // 必须在定义后调用,否则会抛出错误
greet(); // 输出 "Hello!"
特点函数声明函数表达式
提升(Hoisting)会被提升,可以在声明之前调用不会被提升,必须在定义后调用
命名一般是命名的,支持 arguments 对象可以是匿名的,不支持 arguments 
作用域在其定义的作用域处可见仅能在定义之后的作用域内可见
适用场景用于定义可重复使用的函数用于动态创建函数、回调或立即执行的场合

总结

JavaScript 的函数既强大又灵活,支持多种参数的表现形式和高级概念。掌握这些函数的分类、参数处理和高级函数的使用,能够大大提升 JavaScript 编程的能力和代码的可读性。通过练习这些概念,能够更深入理解 JavaScript 的函数特性及其应用。