六、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
返回 a
和 b
的和,并将结果赋值给变量 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
- 使用
return
的注意事项
return
语句只能在函数内部使用。- 一旦执行到
return
语句,函数会立即停止执行,后面的代码将不会被执行。 - 在函数中如果没有明确返回值,默认返回
undefined
。
- 总结
return
关键字用于从函数中返回值并结束函数执行。- 可以返回基本数据类型、对象、数组等。
- 通过条件判断,可以在满足某些条件时提前退出函数。
- 理解
return
的使用对于编写高效和清晰的代码至关重要
6.3 函数的递归调用
在JavaScript
中,递归调用指的是一个函数在其函数体内调用自身。递归通常用于解决问题可以被分解为更小的同类子问题的情况,比如计算阶乘、斐波那契数列、遍历树形结构等。
1. 递归函数的基本结构
递归函数的结构通常包含两个部分:
- 基准条件(终止条件):规定递归何时停止的条件,防止无限递归。
- 递归步骤:函数在每次调用时将问题分解为一个更小的子问题,然后调用自身处理这个子问题。
示例:阶乘计算
阶乘 n!
的定义是:
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) = 0
,f(1) = 1
,f(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的第五项)
- 递归调用的优缺点
优点
- 简洁:递归让代码更简洁,尤其是在处理树形或分解性问题时。
- 可读性强:递归实现的代码通常贴近问题的数学定义。
缺点
- 性能问题:每次递归调用都会创建新的函数调用栈,过多的递归会导致栈溢出(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)); // 更快输出结果
- 总结
- 递归调用是一种函数调用自身的编程方法,通常有基准条件和递归步骤。
- 递归适合解决结构重复或可分解为子问题的问题,如阶乘、斐波那契数列、树形结构等。
- 对于效率较低的递归问题,可以通过记忆化或转换为迭代实现进行优化。
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. var
、let
和 const
的作用域区别
JavaScript的 var
、let
和 const
三种声明变量的方式在作用域上有明显区别:
3.1 var
声明的变量
- 函数作用域:用
var
声明的变量在其所在函数中是局部变量。 - 全局作用域:如果
var
在函数外声明,则它是全局变量。 - 变量提升:
var
声明的变量会被提升到作用域顶部,但值不会提升。
示例:
javascript复制代码function testVar() {
if (true) {
var x = 10;
}
console.log(x); // 输出: 10,因为x在函数内有效
}
testVar();
3.2 let
和 const
声明的变量
- 块作用域:用
let
和const
声明的变量仅在其所在块{ }
中有效。 - 无变量提升:
let
和const
声明的变量不会被提升。
示例:
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
则有块作用域。- 使用合适的作用域能够提高代码的模块化、避免命名冲突、减少潜在错误。
欲知后事如何,且听下回分解...