Day19(前端:JavaScript基础阶段)
接续上文:Day18 (前端:JavaScript基础阶段)-CSDN博客
点关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!!
主页:一位搞嵌入式的 genius-CSDN博客
系列文章专栏:
https://blog.csdn.net/m0_73589512/category_13011829.html
目录
JavaScript 函数全解析:从基础到高级应用
一、函数的声明与调用
1.1 函数声明的基本语法
1.2 函数的调用方式
二、函数的参数:形参与实参
2.1 形参与实参的定义
2.2 参数的传递规则
2.3 ES6 中的默认参数
2.4 案例:封装数字格式化工具函数
2.5 arguments 对象:获取所有实参
三、函数的返回值
3.1 返回值的基本规则
3.2 函数作用域隔离
四、函数递归
4.1 递归的实现条件
4.2 案例:实现 x 的 n 次幂
4.3 递归的适用场景
五、函数作用域与变量类型
5.1 作用域的类型
5.2 变量的类型:全局、局部、外部变量
六、函数的定义方式:声明与表达式
6.1 函数声明
6.2 函数表达式
6.3 两种定义方式的核心区别
七、函数的头等公民特性与函数式编程
7.1 头等公民的核心特性
7.2 函数式编程思想
八、回调函数与匿名函数
8.1 回调函数的定义与作用
8.2 匿名函数作为回调函数
九、总结与实践建议
核心知识点回顾
实践建议
JavaScript 函数全解析:从基础到高级应用
函数是 JavaScript 中最为核心的概念之一,它不仅是代码复用的基本单元,更是实现复杂逻辑、异步编程的基础。本文将从函数的声明与调用开始,逐步深入函数的参数、返回值、作用域、递归等特性,最终探讨函数式编程的核心思想,帮助读者全面掌握 JavaScript 函数的用法与精髓。
一、函数的声明与调用
函数本质上是一段封装好的可执行代码块,通过函数名可以重复调用,避免代码冗余。在 JavaScript 中,函数的声明与调用有明确的语法规范,掌握这些基础是后续学习的前提。
1.1 函数声明的基本语法
函数声明使用 function
关键字,基本结构如下:
// 标准函数声明语法 function 函数名(参数列表) {// 函数体:需要执行的代码 }
示例:声明一个计算两个数字之和的函数
// 函数声明:参数列表为 num1 和 num2,函数体返回两者之和 function addNumber(num1, num2) {return num1 + num2; }
注意事项:
-
函数名遵循标识符命名规则(字母、数字、下划线、$ 组成,不能以数字开头);
-
参数列表中的参数之间用逗号分隔;
-
函数体用花括号
{}
包裹,内部可包含任意数量的语句; -
标准编码规范中,函数名后的括号与参数列表之间需保留一个空格(如
addNumber(num1, num2)
而非addNumber(num1, num2)
)。
1.2 函数的调用方式
声明函数后,通过「函数名 + 括号」即可调用函数,若函数需要参数,需在括号中传入对应的值:
// 调用 addNumber 函数,传入实参 3 和 5 const result = addNumber(3, 5); console.log(result); // 输出:8
调用逻辑:
-
程序执行到函数调用语句时,会暂停当前代码执行;
-
跳转到函数体内部,按顺序执行函数内的代码;
-
函数执行完毕(遇到
return
或函数体结束)后,返回调用位置继续执行后续代码。
特殊情况:若函数无需参数,调用时仍需保留空括号(否则会返回函数本身而非执行结果):
function sayHello() {console.log("Hello, World!"); } sayHello(); // 正确调用:输出 "Hello, World!" sayHello; // 错误:返回函数本身,不执行函数体
二、函数的参数:形参与实参
参数是函数的「输入」,它让函数摆脱固定值的限制,能够处理不同的数据,极大提升了函数的通用性。JavaScript 中参数分为「形参」和「实参」,两者既有关联又有区别。
2.1 形参与实参的定义
-
形参(形式参数):函数声明时括号中定义的参数,相当于函数内部的局部变量,仅在函数体内有效。
function multiply(a, b) { // a 和 b 是形参return a * b; }
-
实参(实际参数):函数调用时括号中传入的值,用于给形参赋值,决定函数的具体计算对象。
multiply(4, 5); // 4 和 5 是实参,分别赋值给形参 a 和 b
2.2 参数的传递规则
-
顺序对应:实参与形参按顺序一一对应,第一个实参给第一个形参赋值,第二个实参给第二个形参赋值,以此类推。
function introduce(name, age) {console.log(`我叫${name},今年${age}岁`); } introduce("小明", 18); // 输出:我叫小明,今年18岁
-
参数数量不匹配的处理:
-
实参数量 > 形参数量:多余的实参不会被形参接收,但可通过
arguments
对象获取(详见 2.5 节)。
function sum(a, b) {console.log(a + b); // 仅计算前两个参数 } sum(1, 2, 3); // 实参 3 未被使用,输出:3
-
实参数量 < 形参数量:未被赋值的形参值为
undefined
。
function subtract(a, b) {console.log(a - b); } subtract(5); // 形参 b 为 undefined,输出:NaN(5 - undefined 结果为 NaN)
-
2.3 ES6 中的默认参数
为解决「实参数量不足导致形参为 undefined
」的问题,ES6 引入了「默认参数」特性,允许在声明形参时指定默认值:
// 给形参 b 设置默认值 0 function subtract(a, b = 0) {return a - b; } console.log(subtract(5)); // 实参不足,b 使用默认值 0,输出:5 console.log(subtract(5, 3)); // 实参覆盖默认值,输出:2
默认参数的生效条件:仅当实参未传递或传递 undefined
时,默认值才会生效:
function greet(name = "Guest") {console.log(`Hello, ${name}!`); } greet(); // 未传递实参,使用默认值:Hello, Guest! greet(undefined); // 传递 undefined,使用默认值:Hello, Guest! greet("Alice"); // 传递实参,覆盖默认值:Hello, Alice!
2.4 案例:封装数字格式化工具函数
实际开发中,经常需要将大数字转换为「万」「亿」单位(如统计数据、粉丝数量展示)。利用参数和条件判断,可封装一个通用的格式化函数:
/*** 数字格式化函数:将数字转换为"万"或"亿"单位* @param {number} count - 需要格式化的数字* @returns {string|number} 格式化后的结果*/ function formatCount(count) {if (count > 1_000_000_000) { // 大于10亿:转换为"亿"单位(取整数部分)return Math.floor(count / 100_000_000) + "亿";} else if (count > 100_000) { // 大于10万:转换为"万"单位(取整数部分)return Math.floor(count / 10_000) + "万";} else { // 小于等于10万:直接返回原数字return count;} } // 测试函数 console.log(formatCount(13687)); // 输出:13687 console.log(formatCount(5430000)); // 输出:543万 console.log(formatCount(8700000000)); // 输出:87亿
代码说明:
-
数字中的下划线(如
1_000_000_000
)是 ES6 引入的语法糖,用于分隔数字位数,提升可读性(不会影响数值本身); -
Math.floor()
用于取整数部分,确保结果符合日常阅读习惯(如 543.6 万显示为 543 万)。
2.5 arguments 对象:获取所有实参
在非箭头函数中,函数内部默认存在一个 arguments
对象,它存储了所有传入的实参,无论实参数量是否与形参匹配。
arguments 的特性:
-
类似数组(array-like),但不是真正的数组(没有
forEach
、map
等数组方法); -
通过索引访问实参(
arguments[0]
是第一个实参,arguments[1]
是第二个,以此类推); -
通过
arguments.length
获取实参的数量。
示例:利用 arguments 实现一个求和函数,支持任意数量的参数:
function sumAll() {let total = 0;// 遍历 arguments 中的所有实参,累加求和for (let i = 0; i < arguments.length; i++) {total += arguments[i];}return total; } console.log(sumAll(1, 2)); // 输出:3 console.log(sumAll(3, 4, 5)); // 输出:12 console.log(sumAll(10, 20, 30, 40)); // 输出:100
注意事项:
-
箭头函数中没有
arguments
对象,若需获取所有实参,应使用 ES6 剩余参数(...args
); -
剩余参数是数组,支持数组方法,更推荐使用:
// 剩余参数 ...args 接收所有实参,返回数组 function sumAll(...args) {return args.reduce((total, num) => total + num, 0); }
三、函数的返回值
函数的返回值是函数执行后的「输出」,通过 return
关键字指定。理解返回值的规则,能帮助我们更灵活地设计函数逻辑。
3.1 返回值的基本规则
-
默认返回值:若函数未写
return
或return
后无内容,函数默认返回undefined
。function noReturn() {// 无 return 语句 } console.log(noReturn()); // 输出:undefined function emptyReturn() {return; // return 后无内容 } console.log(emptyReturn()); // 输出:undefined
-
return 的终止作用:
return
语句执行后,函数会立即停止执行,后续代码不再运行。function checkNum(num) {if (num > 10) {return "大于10"; // 满足条件时返回,后续代码不执行}return "小于等于10"; } console.log(checkNum(15)); // 输出:大于10
-
返回值的类型:函数可以返回任意类型的值(数字、字符串、对象、函数等)。
// 返回对象 function createUser(name, age) {return { name, age }; } console.log(createUser("张三", 20)); // 输出:{ name: '张三', age: 20 } // 返回函数 function createAdder(num) {return function (x) {return x + num;}; } const add5 = createAdder(5); console.log(add5(3)); // 输出:8(3 + 5)
3.2 函数作用域隔离
函数内部声明的变量与外部变量(即使同名)互不影响,这种特性称为「作用域隔离」。
示例:
let message = "全局变量"; function showMessage() {let message = "局部变量"; // 函数内部的同名变量console.log(message); // 输出:局部变量(优先使用内部变量) } showMessage(); console.log(message); // 输出:全局变量(外部变量不受影响)
作用:避免变量污染,确保函数内部逻辑的独立性。
四、函数递归
递归是一种特殊的函数调用方式:函数内部调用自身。它的核心价值在于将复杂问题分解为重复的简单子任务,但需注意控制调用次数,避免无限递归。
4.1 递归的实现条件
递归必须满足两个条件:
-
终止条件:当满足某一条件时,停止递归调用(否则会导致无限递归,引发栈溢出错误);
-
问题规模递减:每次递归调用时,问题的规模应逐渐缩小(向终止条件靠近)。
4.2 案例:实现 x 的 n 次幂
计算 x 的 n 次幂(xⁿ)可以通过递归实现,基于数学公式:xⁿ = x × xⁿ⁻¹。
/*** 递归计算 x 的 n 次幂* @param {number} x - 底数* @param {number} n - 指数(正整数)* @returns {number} x 的 n 次幂结果*/ function power(x, n) {// 终止条件:n = 1 时,返回 x(x¹ = x)if (n === 1) {return x;}// 递归调用:xⁿ = x × xⁿ⁻¹(问题规模 n 递减)return x * power(x, n - 1); } console.log(power(2, 3)); // 输出:8(2 × 2 × 2) console.log(power(5, 2)); // 输出:25(5 × 5)
与循环的对比:
实现方式 | 核心逻辑 | 优缺点 |
---|---|---|
for 循环 | 通过迭代累乘实现幂运算 | 性能高,适合简单场景,但代码略显冗长 |
递归 | 基于数学公式分解问题 | 代码简洁,逻辑清晰,但递归深度过大会消耗较多栈内存,可能导致栈溢出 |
4.3 递归的适用场景
递归适合解决可分解为同类子问题的场景,如:
-
树形结构遍历(DOM 树操作、文件夹遍历);
-
分治算法(快速排序、归并排序);
-
数学问题(斐波那契数列、阶乘计算)。
五、函数作用域与变量类型
作用域定义了变量的可访问范围,JavaScript 中作用域的类型和变量的访问规则是理解函数行为的关键。
5.1 作用域的类型
ES5 及之前的版本中,作用域分为两种:
-
全局作用域:在
<script>
标签顶层或函数外部声明的变量,可在程序任何位置访问; -
函数作用域:函数内部声明的变量,仅在函数体内可访问。
示例:
// 全局作用域变量:在整个脚本中可访问 let globalVar = "全局变量"; function testScope() {// 函数作用域变量:仅在 testScope 内部可访问let functionVar = "函数变量";console.log(globalVar); // 输出:全局变量(可访问全局变量)console.log(functionVar); // 输出:函数变量 } testScope(); console.log(globalVar); // 输出:全局变量 console.log(functionVar); // 报错:ReferenceError: functionVar is not defined(无法访问函数内部变量)
ES6 新增的块级作用域:
ES6 引入 let
和 const
关键字,支持「块级作用域」—— 由 {}
包裹的代码块(如 if
、for
、while
语句的代码块)会形成独立作用域,变量仅在块内有效。
if (true) {let blockVar = "块级变量"; // let 声明的变量,仅在 if 块内有效console.log(blockVar); // 输出:块级变量 } console.log(blockVar); // 报错:ReferenceError: blockVar is not defined
注意:var
声明的变量没有块级作用域,在代码块外仍可访问:
if (true) {var varVar = "var 变量"; // var 声明的变量,无块级作用域 } console.log(varVar); // 输出:var 变量(块外可访问)
5.2 变量的类型:全局、局部、外部变量
根据作用域和定义位置,变量可分为三类:
变量类型 | 定义位置 | 访问范围 | 示例 |
---|---|---|---|
全局变量 | <script> 顶层或函数外部 | 整个程序(所有函数内部均可访问) | let global = "全局"; |
局部变量 | 函数内部 | 仅限定义它的函数内部 | function fn() { let local = "局部"; } |
外部变量 | 外层函数或全局作用域 | 被内层函数访问的非自身定义的变量 | function outer() { let outerVar = "外部"; function inner() { console.log(outerVar); } } |
六、函数的定义方式:声明与表达式
JavaScript 中函数有两种主要定义方式:函数声明和函数表达式。两者在语法、创建时机和调用规则上存在显著差异,理解这些差异有助于编写更符合场景的代码。
6.1 函数声明
函数声明是最常见的定义方式,使用 function
关键字直接声明,语法如下:
// 函数声明 function functionName(parameters) {// 函数体 }
特点:
-
函数提升:函数声明会被 JavaScript 引擎提前解析(“提升” 到当前作用域顶部),因此可以在函数声明之前调用函数。
// 提前调用函数(不会报错) sayHi(); // 输出:Hi! // 函数声明 function sayHi() {console.log("Hi!"); }
-
独立语句:函数声明必须作为独立语句存在,不能嵌套在其他表达式中(如赋值语句右侧)。
6.2 函数表达式
函数表达式是将函数作为值赋值给变量的方式,语法如下:
// 函数表达式(匿名函数) const functionName = function(parameters) {// 函数体 }; // 函数表达式(命名函数,函数名仅在内部有效) const sum = function add(a, b) {return a + b; };
特点:
-
无函数提升:函数表达式在代码执行到赋值语句时才会创建,因此不能在定义前调用。
// 提前调用会报错 calculate(2, 3); // 报错:ReferenceError: calculate is not defined // 函数表达式 const calculate = function(a, b) {return a * b; };
-
可匿名:函数表达式中的函数可以省略名称(匿名函数),通过变量名调用即可。
-
作为值使用:函数表达式可以作为其他函数的参数、返回值,或存储在数据结构中(体现 “函数是一等公民” 的特性)。
6.3 两种定义方式的核心区别
对比维度 | 函数声明 | 函数表达式 |
---|---|---|
语法形式 | function 名称() {} | const 变量 = function() {} |
函数提升 | 支持(可在定义前调用) | 不支持(必须在定义后调用) |
命名要求 | 必须指定函数名 | 可匿名(通过变量名调用) |
使用场景 | 通用函数定义,优先考虑 | 作为值传递(如回调函数)、临时函数 |
开发建议:
-
优先使用函数声明,代码可读性更高,且支持提前调用;
-
函数表达式适用于 “函数作为值传递” 的场景(如回调函数、动态生成函数)。
七、函数的头等公民特性与函数式编程
JavaScript 中函数被称为 “头等公民”(First-class Citizen),这意味着函数可以像其他值(如数字、字符串)一样被处理。这种特性是函数式编程的基础,极大提升了语言的灵活性。
7.1 头等公民的核心特性
函数作为头等公民,具有以下特性:
-
赋值给变量:函数可以赋值给变量,通过变量名调用。
const greet = function(name) {return `Hello, ${name}!`; }; console.log(greet("Lily")); // 输出:Hello, Lily!
-
作为函数参数:函数可以作为参数传递给另一个函数(称为 “回调函数”)。
// 接收函数作为参数的高阶函数 function processData(data, handler) {return handler(data); } // 作为参数传递的函数 const double = function(num) {return num * 2; }; console.log(processData(5, double)); // 输出:10
-
作为函数返回值:函数可以返回另一个函数,实现逻辑的封装与延迟执行。
// 返回函数的函数 function createMultiplier(factor) {return function(num) {return num * factor;}; } // 生成“乘以3”的函数 const multiplyBy3 = createMultiplier(3); console.log(multiplyBy3(4)); // 输出:12
-
存储在数据结构中:函数可以作为对象的属性或数组的元素存储。
// 存储在对象中 const calculator = {add: function(a, b) { return a + b; },subtract: function(a, b) { return a - b; } }; console.log(calculator.add(5, 3)); // 输出:8 // 存储在数组中 const operations = [function(a) { return a * 2; },function(a) { return a + 10; } ]; console.log(operations[0](5)); // 输出:10(执行第一个函数)
7.2 函数式编程思想
函数式编程是一种以函数为核心的编程范式,它强调:
-
用函数处理数据,避免副作用(如修改外部变量);
-
通过函数的组合与复用实现复杂逻辑;
-
依赖纯函数(输入相同则输出一定相同,无副作用)保证代码可预测性。
示例:使用函数式思想处理数组
// 纯函数:计算平方(无副作用,输入相同则输出相同) const square = function(num) {return num * num; }; // 纯函数:过滤偶数 const isEven = function(num) {return num % 2 === 0; }; // 数据处理:先过滤偶数,再计算平方 const numbers = [1, 2, 3, 4, 5, 6]; const result = numbers.filter(isEven).map(square); console.log(result); // 输出:[4, 16, 36](2², 4², 6²)
优势:
-
代码更简洁、可读性更高;
-
纯函数便于测试和调试;
-
函数组合灵活,可快速实现复杂逻辑。
八、回调函数与匿名函数
回调函数是函数式编程的重要应用,指 “作为参数传递给另一个函数,并在特定时机被调用” 的函数。匿名函数常作为回调函数使用,简化代码结构。
8.1 回调函数的定义与作用
定义:当函数 A 作为参数传递给函数 B,且函数 B 在执行过程中调用了函数 A,则函数 A 称为回调函数。
作用:
-
实现异步操作(如网络请求、定时器);
-
实现事件响应(如点击事件、滚动事件);
-
封装通用逻辑,让函数更灵活(如数组的
map
、filter
方法)。
示例:模拟网络请求的回调函数
// 模拟网络请求的函数,接收回调函数处理结果 function fetchData(url, callback) {console.log(`正在请求 ${url}...`);// 模拟延迟(1秒后返回数据)setTimeout(function() {const data = `来自 ${url} 的数据`;callback(data); // 调用回调函数处理数据}, 1000); } // 定义回调函数:处理请求结果 function handleData(result) {console.log("处理结果:", result); } // 调用 fetchData,传入回调函数 fetchData("https://api.example.com", handleData); // 输出: // 正在请求 https://api.example.com... // (1秒后)处理结果:来自 https://api.example.com 的数据
8.2 匿名函数作为回调函数
匿名函数(无名称的函数)常直接作为回调函数传递,避免为临时函数命名,简化代码:
// 匿名函数作为回调函数(简化上述示例) fetchData("https://api.example.com", function(result) {console.log("处理结果:", result); });
常见场景:
-
定时器(
setTimeout
、setInterval
); -
事件监听(
addEventListener
); -
数组方法(
forEach
、map
、filter
)。
// 数组 forEach 方法中使用匿名回调 const fruits = ["apple", "banana", "orange"]; fruits.forEach(function(fruit, index) {console.log(`第 ${index + 1} 个水果:${fruit}`); }); // 输出: // 第 1 个水果:apple // 第 2 个水果:banana // 第 3 个水果:orange
九、总结与实践建议
函数是 JavaScript 的核心,从基础的声明调用到高级的函数式编程,掌握函数的特性对编写高效、可维护的代码至关重要。
核心知识点回顾
-
函数基础:声明与调用的语法,形参、实参的传递规则;
-
参数与返回值:默认参数、
arguments
对象、返回值的类型与作用; -
作用域:全局作用域、函数作用域、块级作用域的区别,变量的访问规则;
-
递归:终止条件与问题规模递减的必要性,适用场景;
-
函数定义方式:函数声明与表达式的差异,函数提升的影响;
-
头等公民特性:函数作为值传递、返回值、存储在数据结构中的能力;
-
函数式编程:纯函数、回调函数的应用,函数组合的思想。
实践建议
-
命名规范:函数名使用动词或动词短语(如
calculateTotal
、formatDate
),清晰表达函数功能; -
参数设计:参数数量不宜过多(建议不超过 3 个),复杂参数用对象封装;
-
避免副作用:尽量编写纯函数,减少对外部变量的修改;
-
递归优化:深层递归可考虑用循环重构,避免栈溢出;
-
代码复用:将重复逻辑封装为函数,通过参数调整行为,提升复用性。
通过大量练习(如封装工具函数、实现算法),可逐步加深对函数特性的理解,最终灵活运用函数解决复杂问题。