函数
# 函数
# 1. 简介
函数是用于完成特定功能的代码块,可以被调用多次,实现代码的复用。函数的分类包括:
- 内置函数:JavaScript提供的现成函数。
- 自定义函数:由开发者定义的函数。
# 2. 内置函数
JavaScript 提供了一些常用的内置函数,帮助开发者处理常见任务。
函数名 | 含义 |
---|---|
typeof() | 判断变量的类型 |
isNaN() | 判断参数是否为NaN |
parseInt() | 将数据转换为整数 |
parseFloat() | 将数据转换为小数 |
eval() | 计算字符串表达式的值,并执行其中的JavaScript代码 |
encodeURI() | 使用 ISO8859-1 对字符串进行编码,每个 UTF-8 的汉字编码成 3 个 16 进制字节,如 %字节1%字节2%字节3 |
decodeURI() | 对使用 ISO8859-1 编码的字符串进行解码 |
escape() | 使用 Unicode 对字符串进行编码,每个 UTF-8 的汉字被编码成一个双字节 16 进制转义字符 %uXXXX ,中文范围 %u4e00---%u9fa5 |
unescape() | 对使用 Unicode 编码的字符串进行解码 |
# 3. 自定义函数
定义和调用
定义函数的语法:
function 函数名(参数1, 参数2...) { // 形参
// 函数体
}
// 调用函数
函数名(参数1, 参数2...); // 实参
2
3
4
5
6
参数
- 形参:在定义函数时指定的参数,没有实际的值,占位置。
- 实参:在调用函数时传入的参数,有实际的值。
实参个数和形参个数可以不同,未指定参数时其默认值为 undefined
。
返回值
函数执行后可以返回一个结果,称为函数的返回值。
- 使用
return
关键字来返回函数执行后的结果值。 - 如果函数中没有使用
return
返回值,则默认返回undefined
。
# 4. 变量作用域
在 JavaScript 中,变量的作用域决定了变量可以在哪些地方被访问。作用域有助于管理变量的生命周期和可见性。JavaScript 中的作用域包括局部作用域、全局作用域和块级作用域。
# 1. 局部作用域
局部作用域(Local Scope)是指在函数内部声明的变量,它们只能在该函数内访问。函数执行结束后,这些局部变量会被销毁,释放内存。
示例:
function showMessage() {
var message = "Hello, world!"; // 局部变量
console.log(message); // 可以访问局部变量
}
showMessage(); // 输出:Hello, world!
console.log(message); // 报错:message is not defined
2
3
4
5
6
7
在上面的示例中,变量 message
是在函数 showMessage
内部声明的,只能在函数内部访问。函数执行结束后,message
变量被销毁,函数外无法访问该变量。
# 2. 全局作用域
全局作用域(Global Scope)是指在函数外部声明的变量,或者在函数内部未使用 var
、let
、const
关键字直接赋值的变量。全局变量可以在任何地方访问,直到页面关闭或刷新后销毁。
示例:
var globalMessage = "Hello, global!"; // 全局变量
function showMessage() {
console.log(globalMessage); // 可以访问全局变量
}
showMessage(); // 输出:Hello, global!
console.log(globalMessage); // 输出:Hello, global!
2
3
4
5
6
7
8
在上面的示例中,变量 globalMessage
是在函数外部声明的,因此它是一个全局变量,可以在任何地方访问。
注意事项:
如果在函数内部直接赋值而未使用 var
、let
或 const
关键字声明,则该变量也会成为全局变量,这是一个潜在的陷阱,应尽量避免。
示例:
function showMessage() {
message = "Hello, world!"; // 没有使用 var、let、const 关键字,成为全局变量
console.log(message);
}
showMessage(); // 输出:Hello, world!
console.log(message); // 输出:Hello, world!
2
3
4
5
6
7
# 3. 块级作用域
块级作用域(Block Scope)是指在代码块(通常由花括号 {}
包围的代码块)内声明的变量。使用 let
或 const
关键字声明的变量具有块级作用域。
示例:
{
let blockMessage = "Hello, block!"; // 块级作用域变量
console.log(blockMessage); // 可以访问块级作用域变量
}
console.log(blockMessage); // 报错:blockMessage is not defined
2
3
4
5
6
在上面的示例中,变量 blockMessage
是在块内用 let
关键字声明的,因此它只能在该块内访问,块外无法访问。
# 4. 作用域优先级
如果局部变量和全局变量同名,默认访问的是局部变量。JavaScript 在作用域链中查找变量时,会优先查找最近的作用域。
示例:
var message = "Hello, global!"; // 全局变量
function showMessage() {
var message = "Hello, local!"; // 局部变量
console.log(message); // 输出:Hello, local!
}
showMessage();
console.log(message); // 输出:Hello, global!
2
3
4
5
6
7
8
9
在上面的示例中,函数 showMessage
内部的局部变量 message
覆盖了全局变量 message
,函数内部输出的是局部变量的值。
# 5. 访问全局变量
如果在局部作用域中需要访问全局变量,可以使用 window
对象进行访问。在全局作用域中,所有的全局变量都是 window
对象的属性。
示例:
var message = "Hello, global!"; // 全局变量
function showMessage() {
var message = "Hello, local!"; // 局部变量
console.log(message); // 输出:Hello, local!
console.log(window.message); // 输出:Hello, global!
}
showMessage();
2
3
4
5
6
7
8
9
在上面的示例中,使用 window.message
可以访问全局变量 message
,即使在函数内部有一个同名的局部变量。
# 6. var
和 let
的区别
在 JavaScript 中,var
和 let
都可以用于声明变量,但它们之间有一些重要的区别。
# 6.1 作用域
var
:- 函数作用域:
var
声明的变量在函数内是局部变量,只能在函数内部访问。 - 全局作用域:如果
var
在函数外声明,则是全局变量,可以在整个脚本的任何地方访问。 var
没有块级作用域:在代码块(如if
语句、for
循环)内部使用var
声明的变量,其作用域会被提升到函数作用域或全局作用域。
function testVar() { if (true) { var x = 10; } console.log(x); // 输出 10 } testVar();
1
2
3
4
5
6
7- 函数作用域:
let
:- 块级作用域:
let
声明的变量仅在其声明所在的代码块内有效。 - 如果
let
在函数外声明,则是全局变量,但不会成为全局对象(如window
)的属性。
function testLet() { if (true) { let y = 10; } console.log(y); // 报错:y is not defined } testLet();
1
2
3
4
5
6
7- 块级作用域:
# 6.2 变量提升
var
:var
声明的变量会被提升到函数作用域或全局作用域的顶部,但变量初始化不会提升。这意味着变量可以在声明之前使用,但值为undefined
。
console.log(a); // 输出 undefined var a = 5;
1
2let
:let
声明的变量不会被提升到块的顶部。在变量声明之前访问会导致引用错误(ReferenceError)。
console.log(b); // 报错:ReferenceError: Cannot access 'b' before initialization let b = 5;
1
2
# 6.3 重复声明
var
:- 允许在同一作用域内重复声明同一变量。
var c = 10; var c = 20; // 不会报错 console.log(c); // 输出 20
1
2
3let
:- 不允许在同一作用域内重复声明同一变量。
let d = 10; let d = 20; // 报错:SyntaxError: Identifier 'd' has already been declared
1
2
# 6.4 全局对象属性
var
:- 在全局作用域中使用
var
声明的变量会成为全局对象(如window
)的属性。
var e = 10; console.log(window.e); // 输出 10
1
2- 在全局作用域中使用
let
:- 在全局作用域中使用
let
声明的变量不会成为全局对象的属性。
let f = 10; console.log(window.f); // 输出 undefined
1
2- 在全局作用域中使用
总结
var
声明的变量具有函数作用域或全局作用域,没有块级作用域,会变量提升,可以重复声明,并且在全局作用域中声明时会成为全局对象的属性。let
声明的变量具有块级作用域,不会变量提升,不能重复声明,并且在全局作用域中声明时不会成为全局对象的属性。
# 5. 变量提升
变量提升(Hoisting)是 JavaScript 中的一个重要概念,它指的是 JavaScript 解析器在代码执行前会先处理变量和函数的声明,但不会处理变量的赋值操作。理解变量提升有助于避免代码中的一些常见错误。
# 1. 解析器执行 JavaScript 代码的过程
全局预解析阶段:
- 在代码执行之前,解析器会首先扫描整个代码,并将所有的
var
声明和function
声明提前到其所在作用域的最顶端。这一阶段称为预解析阶段或创建阶段。 - 变量的声明(但不包括赋值)和函数的声明会在这个阶段被处理。
console.log(a); // undefined var a = 5; function foo() { console.log("Hello, World!"); }
1
2
3
4
5在上面的代码中,预解析阶段将其处理为:
var a; function foo() { console.log("Hello, World!"); } console.log(a); // undefined a = 5;
1
2
3
4
5
6- 在代码执行之前,解析器会首先扫描整个代码,并将所有的
代码执行阶段:
- 预解析阶段完成后,JavaScript 引擎开始从上到下逐行执行代码。
- 在执行阶段,变量的赋值和函数的调用会按顺序执行。
console.log(a); // undefined var a = 5; // 赋值操作在执行阶段进行 foo(); // 输出 "Hello, World!"
1
2
3函数内部预解析和执行阶段:
- 当 JavaScript 执行函数时,会进入函数作用域,再次进行预解析和执行。
- 函数内部的变量和函数声明也会被提升到函数作用域的顶部。
function bar() { console.log(b); // undefined var b = 10; function baz() { console.log("Inside baz"); } baz(); // 输出 "Inside baz" } bar();
1
2
3
4
5
6
7
8
9在上面的代码中,函数
bar
的预解析阶段将其处理为:function bar() { var b; function baz() { console.log("Inside baz"); } console.log(b); // undefined b = 10; baz(); // 输出 "Inside baz" } bar();
1
2
3
4
5
6
7
8
9
10
# 2. 需要注意的几点
变量声明提升但赋值不提升:
- 变量声明会被提升,但赋值操作仍在原来的位置进行。
console.log(x); // undefined var x = 10; console.log(x); // 10
1
2
3预解析阶段:
var x; console.log(x); // undefined x = 10; console.log(x); // 10
1
2
3
4let
和const
不会被提升:- 使用
let
和const
声明的变量不会被提升,它们在声明前是不可访问的。如果在声明前访问会导致引用错误(ReferenceError)。
console.log(y); // ReferenceError: Cannot access 'y' before initialization let y = 20;
1
2- 使用
函数声明提升:
- 函数声明会被提升,并且可以在声明前调用。
greet(); // 输出 "Hello!" function greet() { console.log("Hello!"); }
1
2
3
4预解析阶段:
function greet() { console.log("Hello!"); } greet(); // 输出 "Hello!"
1
2
3
4函数表达式不提升:
- 使用变量声明的函数表达式不会被提升,因为只有变量声明被提升,而赋值部分仍在原来的位置。
console.log(sum); // undefined var sum = function(a, b) { return a + b; }; console.log(sum(1, 2)); // 3
1
2
3
4
5预解析阶段:
var sum; console.log(sum); // undefined sum = function(a, b) { return a + b; }; console.log(sum(1, 2)); // 3
1
2
3
4
5
6
总结
- 变量声明使用
var
会被提升到作用域顶部,但赋值操作不会被提升。 - 函数声明会被提升,可以在声明前调用。
- 使用
let
和const
声明的变量不会被提升,在声明前访问会导致引用错误。 - 函数表达式不会被提升,只有变量声明会被提升,但赋值操作不会被提升。
# 6. 定义函数的方式
JavaScript 提供了多种定义函数的方式,最常见的包括函数声明和函数表达式。这两种方式在实际应用中各有优劣,可以根据具体情况选择使用。
# 1. 函数声明
语法:
function 函数名(参数) {
// 函数体
}
2
3
示例:
function greet(name) { // 定义一个名为 greet 的函数,接收一个参数 name
console.log("Hello, " + name + "!");
}
greet("Alice"); // 调用函数,输出: Hello, Alice!
2
3
4
5
特点:
函数声明提升:函数声明会在代码执行前被解析器提升到其所在作用域的顶部,因此可以在函数声明之前调用该函数。
示例:
console.log(square(5)); // 输出: 25 function square(num) { // 函数声明 return num * num; }
1
2
3
4
5
# 2. 函数表达式
语法:
var 变量名 = function(参数) {
// 函数体
};
变量名(参数); // 调用函数
2
3
4
5
示例:
var greet = function(name) { // 定义一个匿名函数并赋值给变量 greet
console.log("Hello, " + name + "!");
};
greet("Bob"); // 调用函数,输出: Hello, Bob!
2
3
4
5
特点:
匿名函数:函数表达式通常定义匿名函数,即没有名称的函数。该函数赋值给一个变量后,可以通过变量名调用。
函数表达式不提升:函数表达式不会被提升到作用域顶部,因此必须先定义,再调用。
示例:
console.log(square(5)); // 报错: square is not a function var square = function(num) { // 函数表达式 return num * num; }; console.log(square(5)); // 输出: 25
1
2
3
4
5
6
7
区别总结
函数声明提升:函数声明会被提升,因此可以在声明之前调用;而函数表达式不会被提升,必须先定义再调用。
匿名函数:函数表达式通常定义匿名函数,并赋值给一个变量,而函数声明则直接定义命名函数。
调用方式:函数声明通过函数名直接调用,而函数表达式通过赋值的变量名调用。
# 3, 应用场景
函数声明:适用于定义常规函数,特别是在需要函数提升的场景中。
函数表达式:适用于定义匿名函数和回调函数,特别是在需要动态定义和赋值的场景中。
示例应用场景:
// 函数声明,适用于定义常规函数
function add(a, b) {
return a + b;
}
// 函数表达式,适用于动态定义和赋值
var multiply = function(a, b) {
return a * b;
};
// 使用函数表达式作为回调函数
setTimeout(function() {
console.log("This is a callback function.");
}, 1000);
console.log(add(2, 3)); // 输出: 5
console.log(multiply(2, 3)); // 输出: 6
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 7. 回调函数
回调函数(Callback Function)是一种在其他函数中传递并在特定时间或条件下调用的函数。回调函数的一个重要特点是,它们可以被异步执行,使得JavaScript能够处理事件驱动编程和异步操作。
# 1. 基本概念
回调函数是一种不立即执行的函数调用,满足一定条件时才会执行或者由别的代码调用执行。
function sayHello() {
console.log("Hello!");
}
function executeCallback(callback) {
callback(); // 调用回调函数
}
executeCallback(sayHello); // 输出: Hello!
2
3
4
5
6
7
8
9
在上面的例子中,sayHello
是一个回调函数,传递给 executeCallback
函数,并在 executeCallback
函数内部调用。
应用
- 作为事件绑定的函数,即当事件触发时要执行的函数。
- 作为另一个函数的参数,即将函数作为参数传给另一个函数(函数也是一种数据类型)。
# 2. 应用场景
事件处理: 回调函数在事件处理中的应用非常广泛,例如,用户点击按钮时调用回调函数。
document.getElementById("myButton").addEventListener("click", function() { console.log("Button clicked!"); });
1
2
3异步编程: 回调函数在处理异步操作(如网络请求、定时器等)时非常有用。
setTimeout(function() { console.log("Executed after 2 seconds"); }, 2000);
1
2
3数组方法: JavaScript 的数组方法(如
map
、filter
、forEach
)常常使用回调函数。var numbers = [1, 2, 3, 4, 5]; var doubled = numbers.map(function(number) { return number * 2; }); console.log(doubled); // 输出: [2, 4, 6, 8, 10]
1
2
3
4
5
# 3. 回调函数的类型
同步回调: 同步回调在执行完立即返回结果,例如数组方法中的回调。
// 定义一个处理数组的函数,该函数接受一个数组和一个回调函数作为参数 function processArray(arr, callback) { // 遍历数组中的每个元素 for (var i = 0; i < arr.length; i++) { // 对每个元素执行回调函数 callback(arr[i]); } } // 调用 processArray 函数,传入一个数组和一个匿名回调函数 processArray([1, 2, 3], function(element) { // 在回调函数中输出当前元素 console.log(element); // 依次输出: 1, 2, 3 });
1
2
3
4
5
6
7
8
9
10
11
12
13
14异步回调: 异步回调在将来的某个时间点执行,例如定时器和网络请求。
// 定义一个模拟数据获取的函数,该函数接受一个回调函数作为参数 function fetchData(callback) { // 使用 setTimeout 模拟网络请求,延迟 2 秒后执行回调函数 setTimeout(function() { // 调用回调函数,并传递模拟的获取到的数据 callback("Data fetched"); }, 2000); } // 调用 fetchData 函数,传入一个匿名回调函数 fetchData(function(data) { // 在回调函数中输出获取到的数据 console.log(data); // 输出: Data fetched (在 2 秒后) });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 8. 匿名函数
匿名函数是没有名字的函数,通常用于回调函数、一次性执行的函数、自执行函数等场景。匿名函数能够使代码更加简洁和灵活。
# 1. 用于事件的回调
在事件处理程序中,常常使用匿名函数作为回调函数,以便在事件触发时执行特定操作。
示例:
window.onclick = function() {
// 这个匿名函数将在页面被点击时执行
console.log("点击了页面!");
};
2
3
4
在这个示例中,当页面被点击时,匿名函数会被调用并输出 "点击了页面!"。这里匿名函数被直接赋值给 onclick
事件属性。
# 2. 用于一次性执行的函数(自执行函数)
自执行函数(IIFE,Immediately Invoked Function Expression)是一种定义后立即执行的匿名函数。通常用于创建独立的作用域,避免变量污染全局作用域。
示例:
(function() {
// 这个匿名函数会在定义后立即执行
console.log("此函数只执行一次!");
})();
2
3
4
在这个示例中,匿名函数被定义并立即调用,输出 "此函数只执行一次!"。自执行函数的语法是在函数外部加上括号,然后再跟一对括号用于调用函数。
# 3. 将匿名函数作为另一个函数的参数
匿名函数常用于传递给其他函数作为参数,特别是在需要延迟执行或者异步执行的情况下。
示例:
setTimeout(function() {
// 这个匿名函数将在延迟一秒后执行
console.log("延迟一秒后执行");
}, 1000);
2
3
4
在这个示例中,匿名函数被传递给 setTimeout
,并在延迟一秒后执行,输出 "延迟一秒后执行"。这里的匿名函数作为回调函数,执行了定时操作。
匿名函数的优势
- 代码简洁:不需要单独命名函数,直接定义在需要使用的地方,减少了命名冲突的可能性。
- 局部作用域:特别是自执行函数,可以创建局部作用域,避免全局变量污染。
- 灵活性高:可以随时定义和使用,尤其适用于回调函数和事件处理。
使用匿名函数的注意事项
- 调试困难:匿名函数没有名称,调试时无法通过名称快速定位函数。
- 可读性问题:在复杂的代码中,过多的匿名函数可能降低代码的可读性,建议在必要时使用命名函数。
:::