JavaScript函数(一)

59 阅读9分钟

六、JavaScript函数

6.1 函数的声明和调用

JavaScript中,函数是一段可重用的代码块,可以接收输入(参数),并返回输出。函数的声明和调用是理解JavaScript编程的基础。以下是关于函数声明和调用的详细说明。

1. 函数的声明

函数可以通过多种方式进行声明,主要有以下三种:

1.1 函数声明(Function Declaration)

使用 function 关键字定义函数,通常用于创建具有名称的函数。

语法:

function functionName(parameters) {
    // 函数体
    return value; // 可选
}

示例:

function greet(name) {
    return "Hello, " + name + "!";
}

1.2 函数表达式(Function Expression)

函数可以被定义为一个表达式,并可以赋值给变量。可以是命名或匿名函数。

示例:

const greet = function(name) {
    return "Hello, " + name + "!";
};

1.3 箭头函数(Arrow Function)

ES6引入的箭头函数是一种更简洁的函数表达式语法,特别适合用于简短的函数。

示例:

const greet = (name) => {
    return "Hello, " + name + "!";
};

// 或者更简洁的写法(当只有一条语句时)
const greet = name => "Hello, " + name + "!";

2. 函数的调用

一旦函数被声明,你可以通过函数名后跟括号来调用它,并可以传递参数。

2.1 调用函数

调用函数的基本语法如下:

functionName(arguments);

示例:

const message = greet("Alice"); // 调用greet函数
console.log(message); // 输出: Hello, Alice!

2.2 函数参数

你可以在调用函数时传递不同数量的参数。JavaScript会忽略多余的参数,并将未传递的参数设置为undefined

示例:

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

console.log(add(5, 10)); // 输出: 15
console.log(add(5)); // 输出: NaN (因为b是undefined)

2.3 返回值

函数可以使用 return 语句返回一个值。一旦return语句执行,函数会立即终止并返回指定的值。

示例:

function square(num) {
    return num * num;
}

const result = square(4);
console.log(result); // 输出: 16

3. 函数的作用域

在JavaScript中,函数具有作用域,函数内部定义的变量在外部不可见。

示例:

function example() {
    let x = 10; // x是局部变量
    console.log(x);
}

example(); // 输出: 10
// console.log(x); // Uncaught ReferenceError: x is not defined

4. 总结

  • 函数声明:使用 function 关键字创建,可以命名。
  • 函数表达式:函数作为表达式,可以赋值给变量。
  • 箭头函数:使用箭头语法简化函数表达式。
  • 函数调用:通过函数名和括号调用,并可传递参数。
  • 返回值:函数可以返回值,使用 return 语句终止函数执行。
  • 作用域:函数内部的变量在外部不可见,形成封闭的作用域。

理解函数的声明和调用是JavaScript编程的基础,它们在构建模块化、可重用代码时起着至关重要的作用。

6.2 return关键字

JavaScript中,return 关键字用于从函数中返回一个值并终止函数的执行。它是函数的重要组成部分,允许你将结果传递回调用该函数的地方。

1. 使用 return 关键字

1.1 返回值

当你在函数中使用 return 语句时,函数将停止执行,并返回 return 后面指定的值。

示例:

function add(a, b) {
    return a + b; // 返回两个参数的和
}

const result = add(5, 10);
console.log(result); // 输出: 15

在这个例子中,函数 add 返回 ab 的和,并将结果赋值给变量 result

1.2 没有返回值

如果没有使用 return 语句,或者直接使用 return 而不指定返回值,则该函数会返回 undefined

示例:

function greet(name) {
    console.log("Hello, " + name + "!");
    // 没有return语句
}

const message = greet("Alice");
console.log(message); // 输出: Hello, Alice! 并且 message 是 undefined

2. 多个返回值

JavaScript 的函数一次只能返回一个值。如果你想返回多个值,可以将它们放入一个数组或对象中。

示例:

function getCoordinates() {
    return [10, 20]; // 返回一个数组
}

const coordinates = getCoordinates();
console.log(coordinates); // 输出: [10, 20]

或者使用对象:

function getUser() {
    return { name: "Alice", age: 25 }; // 返回一个对象
}

const user = getUser();
console.log(user.name); // 输出: Alice

3**. 提前退出函数**

return 语句也可以用于在特定条件下提前退出函数。这对于控制函数的执行流程非常有用。

示例:

function checkAge(age) {
    if (age < 18) {
        return "Access denied"; // 早期返回
    }
    return "Access granted";
}

console.log(checkAge(16)); // 输出: Access denied
console.log(checkAge(20)); // 输出: Access granted
  1. 使用 return 的注意事项
  • return 语句只能在函数内部使用。
  • 一旦执行到 return 语句,函数会立即停止执行,后面的代码将不会被执行。
  • 在函数中如果没有明确返回值,默认返回 undefined
  1. 总结
  • return 关键字用于从函数中返回值并结束函数执行。
  • 可以返回基本数据类型、对象、数组等。
  • 通过条件判断,可以在满足某些条件时提前退出函数。
  • 理解 return 的使用对于编写高效和清晰的代码至关重要

6.3 函数的递归调用

JavaScript中,递归调用指的是一个函数在其函数体内调用自身。递归通常用于解决问题可以被分解为更小的同类子问题的情况,比如计算阶乘、斐波那契数列、遍历树形结构等。

1. 递归函数的基本结构

递归函数的结构通常包含两个部分:

  • 基准条件(终止条件):规定递归何时停止的条件,防止无限递归。
  • 递归步骤:函数在每次调用时将问题分解为一个更小的子问题,然后调用自身处理这个子问题。

示例:阶乘计算

阶乘 n! 的定义是:

  • 0!=10!=1
  • n!=n×(n1)!(其中n>0n!=n×(n−1)!(其中 n>0)
function factorial(n) {
    if (n <= 1) { // 基准条件
        return 1;
    }
    return n * factorial(n - 1); // 递归步骤
}

console.log(factorial(5)); // 输出: 120 (5*4*3*2*1)

2. 递归调用的工作原理

每次函数调用时,JavaScript 会将当前的函数执行放在调用栈中,并等待这个调用返回结果。在递归调用中,每个递归步骤会加入栈中,直到满足基准条件返回结果后,调用栈开始一层层地弹出结果。

3. 示例:斐波那契数列

斐波那契数列的定义为:f(0) = 0f(1) = 1f(n) = f(n-1) + f(n-2)

递归实现斐波那契数列:

function fibonacci(n) {
    if (n <= 1) { // 基准条件
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2); // 递归步骤
}

console.log(fibonacci(5)); // 输出: 5 (即0, 1, 1, 2, 3, 5的第五项)
  1. 递归调用的优缺点

优点

  • 简洁:递归让代码更简洁,尤其是在处理树形或分解性问题时。
  • 可读性强:递归实现的代码通常贴近问题的数学定义。

缺点

  • 性能问题:每次递归调用都会创建新的函数调用栈,过多的递归会导致栈溢出(Stack Overflow)。
  • 效率较低:如斐波那契数列这种重复计算的递归,效率低下。

5. 优化递归:尾递归和记忆化

尾递归:在一些支持尾递归优化的语言中(JavaScript目前不支持),将递归调用作为最后一步可以减少调用栈的大小。但在JavaScript中尾递归不会有性能上的提升。

记忆化:用一个对象保存已经计算过的结果,避免重复计算,常用于有重叠子问题的递归问题(如斐波那契数列)。

示例:记忆化斐波那契数列

function fibonacciMemo(n, memo = {}) {
    if (n <= 1) {
        return n;
    }
    if (memo[n]) { // 检查memo中是否已有结果
        return memo[n];
    }
    memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo); // 计算并存储
    return memo[n];
}

console.log(fibonacciMemo(50)); // 更快输出结果
  1. 总结
  • 递归调用是一种函数调用自身的编程方法,通常有基准条件和递归步骤。
  • 递归适合解决结构重复或可分解为子问题的问题,如阶乘、斐波那契数列、树形结构等。
  • 对于效率较低的递归问题,可以通过记忆化或转换为迭代实现进行优化。

6.4 局部和全局变量

JavaScript中,变量可以根据它们的作用域分为全局变量局部变量。理解局部和全局变量及其作用域范围对编写清晰、维护性好的代码至关重要。

1. 全局变量

全局变量是指在函数、块或代码片段之外声明的变量。它们可以在代码的任何地方访问。

特点:

  • 在整个代码文件中都有效,能够在任何函数或块中访问。
  • 在浏览器环境中,全局变量会自动成为 window 对象的属性(在Node.js中为 global 对象)。

示例:

var globalVar = "I am a global variable"; // 全局变量

function printGlobalVar() {
    console.log(globalVar); // 可以在函数中访问全局变量
}

printGlobalVar(); // 输出: I am a global variable
console.log(globalVar); // 输出: I am a global variable

2. 局部变量

局部变量是只在特定作用域(如函数或块中)内有效的变量。它们在定义的作用域之外无法访问。

特点:

  • 局部变量在函数或块中声明,只能在该函数或块中访问。
  • 局部变量在函数调用结束后被销毁,因此不会在全局作用域中产生影响。

示例:

function printLocalVar() {
    var localVar = "I am a local variable"; // 局部变量
    console.log(localVar); // 输出: I am a local variable
}

printLocalVar();
// console.log(localVar); // Uncaught ReferenceError: localVar is not defined

3. varletconst 的作用域区别

JavaScript的 varletconst 三种声明变量的方式在作用域上有明显区别:

3.1 var 声明的变量

  • 函数作用域:用 var 声明的变量在其所在函数中是局部变量。
  • 全局作用域:如果 var 在函数外声明,则它是全局变量。
  • 变量提升var 声明的变量会被提升到作用域顶部,但值不会提升。

示例:

javascript复制代码function testVar() {
    if (true) {
        var x = 10;
    }
    console.log(x); // 输出: 10,因为x在函数内有效
}

testVar();

3.2 letconst 声明的变量

  • 块作用域:用 letconst 声明的变量仅在其所在块 { } 中有效。
  • 无变量提升letconst 声明的变量不会被提升。

示例:

function testLet() {
    if (true) {
        let y = 10;
        const z = 20;
    }
    // console.log(y); // Uncaught ReferenceError: y is not defined
    // console.log(z); // Uncaught ReferenceError: z is not defined
}

testLet();

4. 作用域链

当在一个作用域中访问变量时,JavaScript会沿着作用域链向上查找,直到找到该变量或到达全局作用域。作用域链可以保证局部作用域优先于全局作用域。

示例:

var name = "Alice"; // 全局变量

function greet() {
    var name = "Bob"; // 局部变量
    console.log("Hello, " + name); // 输出: Hello, Bob
}

greet();
console.log(name); // 输出: Alice

在这个例子中,greet 函数中的 name 变量会优先使用局部变量 "Bob",而不会访问全局的 "Alice"

5. 总结

  • 全局变量在程序的任何地方都可访问,可能造成命名冲突。
  • 局部变量只在声明它们的函数或块中有效,能够避免命名冲突。
  • var 有函数作用域,但**let** 和 const 则有块作用域。
  • 使用合适的作用域能够提高代码的模块化、避免命名冲突、减少潜在错误。

欲知后事如何,且听下回分解...