当前位置: 首页 > news >正文

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

调用逻辑

  1. 程序执行到函数调用语句时,会暂停当前代码执行;

  2. 跳转到函数体内部,按顺序执行函数内的代码;

  3. 函数执行完毕(遇到 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 参数的传递规则

  1. 顺序对应:实参与形参按顺序一一对应,第一个实参给第一个形参赋值,第二个实参给第二个形参赋值,以此类推。

    function introduce(name, age) {console.log(`我叫${name},今年${age}岁`);
    }
    introduce("小明", 18); // 输出:我叫小明,今年18岁
  2. 参数数量不匹配的处理

    • 实参数量 > 形参数量:多余的实参不会被形参接收,但可通过

      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),但不是真正的数组(没有 forEachmap 等数组方法);

  • 通过索引访问实参(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 返回值的基本规则

  • 默认返回值:若函数未写 returnreturn 后无内容,函数默认返回 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 递归的实现条件

递归必须满足两个条件:

  1. 终止条件:当满足某一条件时,停止递归调用(否则会导致无限递归,引发栈溢出错误);

  2. 问题规模递减:每次递归调用时,问题的规模应逐渐缩小(向终止条件靠近)。

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 及之前的版本中,作用域分为两种:

  1. 全局作用域:在 <script> 标签顶层或函数外部声明的变量,可在程序任何位置访问;

  2. 函数作用域:函数内部声明的变量,仅在函数体内可访问。

示例

// 全局作用域变量:在整个脚本中可访问
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 引入 letconst 关键字,支持「块级作用域」—— 由 {} 包裹的代码块(如 ifforwhile 语句的代码块)会形成独立作用域,变量仅在块内有效。

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 头等公民的核心特性

函数作为头等公民,具有以下特性:

  1. 赋值给变量:函数可以赋值给变量,通过变量名调用。

    const greet = function(name) {return `Hello, ${name}!`;
    };
    console.log(greet("Lily")); // 输出:Hello, Lily!
  2. 作为函数参数:函数可以作为参数传递给另一个函数(称为 “回调函数”)。

    // 接收函数作为参数的高阶函数
    function processData(data, handler) {return handler(data);
    }
    ​
    // 作为参数传递的函数
    const double = function(num) {return num * 2;
    };
    ​
    console.log(processData(5, double)); // 输出:10
  3. 作为函数返回值:函数可以返回另一个函数,实现逻辑的封装与延迟执行。

    // 返回函数的函数
    function createMultiplier(factor) {return function(num) {return num * factor;};
    }
    ​
    // 生成“乘以3”的函数
    const multiplyBy3 = createMultiplier(3);
    console.log(multiplyBy3(4)); // 输出:12
  4. 存储在数据结构中:函数可以作为对象的属性或数组的元素存储。

    // 存储在对象中
    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 称为回调函数。

作用

  • 实现异步操作(如网络请求、定时器);

  • 实现事件响应(如点击事件、滚动事件);

  • 封装通用逻辑,让函数更灵活(如数组的 mapfilter 方法)。

示例:模拟网络请求的回调函数

// 模拟网络请求的函数,接收回调函数处理结果
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);
});

常见场景

  • 定时器(setTimeoutsetInterval);

  • 事件监听(addEventListener);

  • 数组方法(forEachmapfilter)。

// 数组 forEach 方法中使用匿名回调
const fruits = ["apple", "banana", "orange"];
fruits.forEach(function(fruit, index) {console.log(`第 ${index + 1} 个水果:${fruit}`);
});
// 输出:
// 第 1 个水果:apple
// 第 2 个水果:banana
// 第 3 个水果:orange

九、总结与实践建议

函数是 JavaScript 的核心,从基础的声明调用到高级的函数式编程,掌握函数的特性对编写高效、可维护的代码至关重要。

核心知识点回顾

  1. 函数基础:声明与调用的语法,形参、实参的传递规则;

  2. 参数与返回值:默认参数、arguments 对象、返回值的类型与作用;

  3. 作用域:全局作用域、函数作用域、块级作用域的区别,变量的访问规则;

  4. 递归:终止条件与问题规模递减的必要性,适用场景;

  5. 函数定义方式:函数声明与表达式的差异,函数提升的影响;

  6. 头等公民特性:函数作为值传递、返回值、存储在数据结构中的能力;

  7. 函数式编程:纯函数、回调函数的应用,函数组合的思想。

实践建议

  1. 命名规范:函数名使用动词或动词短语(如 calculateTotalformatDate),清晰表达函数功能;

  2. 参数设计:参数数量不宜过多(建议不超过 3 个),复杂参数用对象封装;

  3. 避免副作用:尽量编写纯函数,减少对外部变量的修改;

  4. 递归优化:深层递归可考虑用循环重构,避免栈溢出;

  5. 代码复用:将重复逻辑封装为函数,通过参数调整行为,提升复用性。

通过大量练习(如封装工具函数、实现算法),可逐步加深对函数特性的理解,最终灵活运用函数解决复杂问题。

http://www.xdnf.cn/news/1436941.html

相关文章:

  • 分布式中防止重复消费
  • Spring Security的@PreAuthorize注解为什么会知道用户角色?
  • 开悟篇Docker从零到实战一篇文章搞定
  • 基于Python毕业设计推荐:基于Django的全国降水分析可视化系统
  • 战略咨询——解读81页中小企业企业战略规划方案【附全文阅读】
  • go-mapus最简单的离线瓦片地图协作
  • C++后端开发重点知识点
  • Adafruit_nRF52_Bootloader 使用 uf2
  • Spring Cloud Config 核心原理
  • 【C++】编写通用模板代码的重要技巧:T()
  • CICD的持续集成与持续交付和Zabbix
  • 【C++】15. ⼆叉搜索树
  • 室内定位---apriltag 视觉定位demo
  • (四)Python控制结构(条件结构)
  • deepseek7b本地部署技巧,新手也能玩得转
  • 下载 | Win11 官方精简版,系统占用空间极少!(8月更新、Win 11 IoT物联网 LTSC版、适合老电脑安装使用)
  • Flink RuntimeContext和FunctionContext:状态计算的核心桥梁
  • Linux中断实验
  • 数字化转型的终极关怀:以人为本
  • Linux笔记14——shell编程基础-8
  • C#类对象映射AutoMapper
  • QT(2)
  • MTK Linux DRM分析(二十九)- MTK mtk_dsi.c(Part.1)
  • Linux 环境配置 muduo 网络库详细步骤
  • Linux 文本处理三大利器:命令小工具和sed
  • 从理念到实践:三层解耦架构与“无系统”论
  • 基于web的高校学籍管理系统的设计与实现-(源码+LW+可部署)
  • CodeBuddy 在进化:我只输入了一个地址,完成了OneCode3.0基础开发环境的配置构建
  • JWT在线解密/JWT在线解码 - 加菲工具
  • kukekey在线搭建k8sV1.30.4版本