这份超全JavaScript函数指南让你从小白变大神

作者:良山有风来日期:2025/10/14

你是不是曾经看着JavaScript里各种函数写法一头雾水?是不是经常被作用域搞得晕头转向?别担心,今天这篇文章就是要帮你彻底搞懂JavaScript函数!

读完本文,你将收获:

  • 函数的各种写法和使用场景
  • 参数传递的底层逻辑
  • 作用域和闭包的彻底理解
  • 箭头函数的正确使用姿势

准备好了吗?让我们开始这场函数探险之旅!

函数基础:从“Hello World”开始

先来看最基础的函数声明方式:

1// 最传统的函数声明
2function sayHello(name) {
3  return "Hello, " + name + "!";
4}
5
6// 调用函数
7console.log(sayHello("小明")); // 输出:Hello, 小明!
8

这里有几个关键点要记住:function是关键字,sayHello是函数名,name是参数,花括号里面是函数体。

但JavaScript的函数写法可不止这一种,还有函数表达式:

1// 函数表达式
2const sayHello = function(name) {
3  return "Hello, " + name + "!";
4};
5
6console.log(sayHello("小红")); // 输出:Hello, 小红!
7

这两种写法看起来差不多,但在底层处理上有些细微差别。函数声明会被提升到作用域顶部,而函数表达式不会。

函数参数:比你想的更灵活

JavaScript的函数参数处理真的很贴心,不像其他语言那么死板:

1function introduce(name, age, city) {
2  console.log("我叫" + name + ",今年" + age + "岁,来自" + city);
3}
4
5// 正常调用
6introduce("张三", 25, "北京"); // 输出:我叫张三,今年25岁,来自北京
7
8// 参数不够 - 缺失的参数会是undefined
9introduce("李四", 30); // 输出:我叫李四,今年30岁,来自undefined
10
11// 参数太多 - 多余的参数会被忽略
12introduce("王五", 28, "上海", "多余参数1", "多余参数2"); // 输出:我叫王五,今年28岁,来自上海
13

看到没?JavaScript不会因为参数个数不匹配就报错,这既是优点也是坑点。

为了解决参数不确定的情况,我们可以用arguments对象或者更现代的rest参数:

1// 使用arguments对象(较老的方式)
2function sum() {
3  let total = 0;
4  for (let i = 0; i < arguments.length; i++) {
5    total += arguments[i];
6  }
7  return total;
8}
9
10console.log(sum(1, 2, 3, 4)); // 输出:10
11
12// 使用rest参数(ES6新特性,推荐!)
13function sum2(...numbers) {
14  return numbers.reduce((total, num) => total + num, 0);
15}
16
17console.log(sum2(1, 2, 3, 4)); // 输出:10
18

rest参数的写法更清晰,而且它是个真正的数组,能用所有数组方法。

作用域深度探秘:变量在哪生效?

作用域可能是JavaScript里最让人困惑的概念之一,但理解它至关重要。

先看个简单例子:

1let globalVar = "我是全局变量"; // 全局变量,在任何地方都能访问
2
3function testScope() {
4  let localVar = "我是局部变量"; // 局部变量,只在函数内部能访问
5  console.log(globalVar); // 可以访问全局变量
6  console.log(localVar); // 可以访问局部变量
7}
8
9testScope();
10console.log(globalVar); // 可以访问
11// console.log(localVar); // 报错!localVar在函数外部不存在
12

但事情没那么简单,看看这个经典的var和let区别:

1// var的怪癖
2function varTest() {
3  if (true) {
4    var x = 10; // var没有块级作用域
5    let y = 20; // let有块级作用域
6  }
7  console.log(x); // 输出:10 - var声明的变量在整个函数都可用
8  // console.log(y); // 报错!y只在if块内可用
9}
10
11varTest();
12

这就是为什么现在大家都推荐用let和const,避免var的奇怪行为。

闭包:JavaScript的超级力量

闭包听起来高大上,其实理解起来并不难:

1function createCounter() {
2  let count = 0; // 这个变量被"封闭"在返回的函数里
3  
4  return function() {
5    count++; // 内部函数可以访问外部函数的变量
6    return count;
7  };
8}
9
10const counter = createCounter();
11console.log(counter()); // 输出:1
12console.log(counter()); // 输出:2
13console.log(counter()); // 输出:3
14

看到神奇之处了吗?count变量本来应该在createCounter执行完就消失的,但因为返回的函数还在引用它,所以它一直存在。

闭包在实际开发中超级有用,比如创建私有变量:

1function createBankAccount(initialBalance) {
2  let balance = initialBalance; // 私有变量,外部无法直接访问
3  
4  return {
5    deposit: function(amount) {
6      balance += amount;
7      return balance;
8    },
9    withdraw: function(amount) {
10      if (amount <= balance) {
11        balance -= amount;
12        return balance;
13      } else {
14        return "余额不足";
15      }
16    },
17    getBalance: function() {
18      return balance;
19    }
20  };
21}
22
23const myAccount = createBankAccount(1000);
24console.log(myAccount.getBalance()); // 输出:1000
25console.log(myAccount.deposit(500)); // 输出:1500
26console.log(myAccount.withdraw(200)); // 输出:1300
27// console.log(balance); // 报错!balance是私有变量,无法直接访问
28

这样我们就实现了数据的封装和保护。

箭头函数:现代JavaScript的利器

ES6引入的箭头函数让代码更简洁:

1// 传统函数
2const add = function(a, b) {
3  return a + b;
4};
5
6// 箭头函数
7const addArrow = (a, b) => {
8  return a + b;
9};
10
11// 更简洁的箭头函数(只有一条return语句时)
12const addShort = (a, b) => a + b;
13
14console.log(add(1, 2)); // 输出:3
15console.log(addArrow(1, 2)); // 输出:3
16console.log(addShort(1, 2)); // 输出:3
17

但箭头函数不只是语法糖,它没有自己的this绑定:

1const obj = {
2  name: "JavaScript",
3  regularFunction: function() {
4    console.log("普通函数this:", this.name);
5  },
6  arrowFunction: () => {
7    console.log("箭头函数this:", this.name); // 这里的this不是obj
8  }
9};
10
11obj.regularFunction(); // 输出:普通函数this: JavaScript
12obj.arrowFunction(); // 输出:箭头函数this: undefined(在严格模式下)
13

这就是为什么在对象方法里通常不用箭头函数。

立即执行函数:一次性的工具

有时候我们需要一个函数只执行一次:

1// 立即执行函数表达式 (IIFE)
2(function() {
3  const secret = "这个变量不会污染全局作用域";
4  console.log("这个函数立即执行了!");
5})();
6
7// 带参数的IIFE
8(function(name) {
9  console.log("Hello, " + name);
10})("世界");
11
12// 用箭头函数写的IIFE
13(() => {
14  console.log("箭头函数版本的IIFE");
15})();
16

在模块化规范出现之前,IIFE是防止变量污染全局的主要手段。

高阶函数:把函数当参数传递

在JavaScript中,函数是一等公民,可以像变量一样传递:

1// 高阶函数 - 接收函数作为参数
2function processArray(arr, processor) {
3  const result = [];
4  for (let i = 0; i < arr.length; i++) {
5    result.push(processor(arr[i]));
6  }
7  return result;
8}
9
10const numbers = [1, 2, 3, 4, 5];
11
12// 传递不同的处理函数
13const doubled = processArray(numbers, function(num) {
14  return num * 2;
15});
16
17const squared = processArray(numbers, function(num) {
18  return num * num;
19});
20
21console.log(doubled); // 输出:[2, 4, 6, 8, 10]
22console.log(squared); // 输出:[1, 4, 9, 16, 25]
23

这就是函数式编程的基础,也是数组方法map、filter、reduce的工作原理。

实战演练:构建一个简单的事件系统

让我们用今天学的知识构建一个实用的小工具:

1function createEventEmitter() {
2  const events = {}; // 存储所有事件和对应的监听器
3  
4  return {
5    // 监听事件
6    on: function(eventName, listener) {
7      if (!events[eventName]) {
8        events[eventName] = [];
9      }
10      events[eventName].push(listener);
11    },
12    
13    // 触发事件
14    emit: function(eventName, data) {
15      if (events[eventName]) {
16        events[eventName].forEach(listener => {
17          listener(data);
18        });
19      }
20    },
21    
22    // 移除监听器
23    off: function(eventName, listenerToRemove) {
24      if (events[eventName]) {
25        events[eventName] = events[eventName].filter(
26          listener => listener !== listenerToRemove
27        );
28      }
29    }
30  };
31}
32
33// 使用示例
34const emitter = createEventEmitter();
35
36// 定义监听器函数
37function logData(data) {
38  console.log("收到数据:", data);
39}
40
41// 监听事件
42emitter.on("message", logData);
43
44// 触发事件
45emitter.emit("message", "你好世界!"); // 输出:收到数据: 你好世界!
46emitter.emit("message", "这是第二条消息"); // 输出:收到数据: 这是第二条消息
47
48// 移除监听器
49emitter.off("message", logData);
50emitter.emit("message", "这条消息不会被接收"); // 不会有输出
51

这个例子用到了我们今天学的几乎所有概念:函数返回函数、闭包、高阶函数等。

常见坑点与最佳实践

学到这里,你已经是函数小能手了!但还要注意这些常见坑点:

1// 坑点1:循环中的闭包
2console.log("=== 循环闭包问题 ===");
3for (var i = 0; i < 3; i++) {
4  setTimeout(function() {
5    console.log(i); // 输出:3, 3, 3 而不是 0, 1, 2
6  }, 100);
7}
8
9// 解决方案1:使用let
10for (let i = 0; i < 3; i++) {
11  setTimeout(function() {
12    console.log(i); // 输出:0, 1, 2
13  }, 100);
14}
15
16// 解决方案2:使用IIFE
17for (var i = 0; i < 3; i++) {
18  (function(j) {
19    setTimeout(function() {
20      console.log(j); // 输出:0, 1, 2
21    }, 100);
22  })(i);
23}
24

最佳实践总结:

  1. 优先使用const,其次是let,避免var
  2. 简单的函数用箭头函数,方法定义用普通函数
  3. 注意this的指向问题
  4. 合理使用闭包,但要注意内存泄漏

总结

恭喜你!现在已经对JavaScript函数有了全面的理解。从基础声明到高级概念,从作用域到闭包,这些都是JavaScript编程的核心基础。

记住,理解函数的关键在于多写代码、多思考。每个概念都要亲手试一试,看看不同的写法会产生什么效果。


这份超全JavaScript函数指南让你从小白变大神》 是转载文章,点击查看原文


相关推荐


武装你的Python“工具箱”:盘点10个你必须熟练掌握的核心方法
烛阴2025/10/12

一、字符串方法 字符串处理是我们日常编程中最高频的操作之一。 .strip() - 去除首尾空白 示例: user_input = " admin \n" cleaned_input = user_input.strip() print(f"清理前: '{user_input}', 清理后: '{cleaned_input}'") # 输出: #清理前: ' admin #', 清理后: 'admin' .split() - 字符串切割 示例: csv_line =


我用亲身经历告诉你,为什么程序员千万别不把英语当回事
oioihoii2025/10/10

年轻人,如果你现在觉得写代码只需要认识 if/else 和 for 循环里的那几个英文单词就够了,那你简直像极了十年前的我。而今天的我,多想回到过去,给那个骄傲自满的自己一记响亮的耳光。 我不是以成功者的姿态来教导你,而是以一个踩过坑、吃过亏、肠子都悔青了的过来人身份,跟你聊聊我最后悔的一件事——没有早点学好英语。 一、工作里吃的哑巴亏,都是我当年脑子进的水 1. “啃”二手资料的酸楚 还记得那次,团队要引入一个热门的新框架。我兴冲冲地找了几篇中文博客,照猫画虎地搞了起来。结果,掉进了一个坑里,


Seata分布式事务框架详解与项目实战
IT橘子皮2025/10/9

一、Seata核心架构与原理 Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务解决方案,旨在为微服务架构提供高性能、易用性的分布式事务支持。其核心设计理念是"化繁为简",通过封装传统分布式事务模式的复杂性,降低分布式一致性问题的解决门槛。 ​核心组件​: ​TC(Transaction Coordinator)​​:事务协调者,维护全局事务和分支事务的状态,负责协调全局事务提交或回滚 ​TM(


【鸿蒙生态共建】一文说清复杂类型数据的非预期输入转换与兜底-《精通HarmonyOS NEXT :鸿蒙App开发入门与项目化实战》读者福利
俩毛豆2025/10/7

在客户端开发中,你是否曾遇到过这样的困扰:一次看似寻常的网络数据解析,却导致了出人意料的崩溃;一个本该正常的文件读取操作,却返回了难以理解的数据错误。这些问题的根源,往往指向同一环节——数据类型转换。当应用面对网络传输、文件I/O等不可控的数据源时,如何稳健、准确地进行数据解析与转换,就成为保障应用稳定性的第一道防线。 本篇内容是《精通HarmonyOS NEXT :鸿蒙App开发入门与项目化实战》这本书第四章内容的延续,是咱这本书读者的福利,在本篇内容中以模拟多种数据输入,向复杂类型(类、数


Spring Cloud之负载均衡之LoadBalance
新绿MEHO2025/10/6

目录 负载均衡 问题 步骤 现象  什么是负载均衡? 负载均衡的一些实现 服务端负载均衡 客户端负载均衡 使用Spring Cloud LoadBalance实现负载均衡 负载均衡策略 ​编辑 ​编辑LoadBalancer原理 服务部署 准备环境和数据 服务构建打包 启动服务 上传Jar包到云服务器 启动服务 远程调用访问  负载均衡 问题 上面是我们之前的代码,是根据应用名称获取了服务实例列表,并从列表中选择了一个服务实例。 那如果


Qwen3 Omni 的“全模态”,到底和多模态有啥不一样?
飞哥数智谈2025/10/5

前一阵阿里云栖大会,其中有个发布内容是全模态大模型 Qwen3-Omni。 说实话,这是我第一次真正地审视“全模态大模型”这个概念,因为之前 Qwen2.5-Omni 发布的时候,有了解过,但不多。 简介 先从概念上简单介绍下什么是“全模态大模型”。 “Qwen3-Omni是新一代原生全模态大模型,能够无缝处理文本、图像、音频和视频等多种输入形式,并通过实时流式响应同时生成文本与自然语音输出。” 多模态大模型 vs 全模态大模型 接下来,为了更好地理解,我们与“多模态大模型”做个对比。 相同点是


【算法导论】PDD 0928 笔试题解
PAK向日葵2025/10/4

快递单号 多多在快递公司负责快递单号录入工作,这些单号有严格的格式要求: 快递单号由3部分组成:2位大写字母(A~Z) + 6位数字 + 1位校验位 校验位计算规则:取前8位(2 字母 + 6 数字)中每个字符的ASCII码之和,对26取余后,加上A的ASCII码,得到的字符即为校验位 现在有一批可能存在校验位错误的单号,请你编写程序: 若单号格式正确且校验位正确,返回原单号 若前 8 位格式正确但校验位错误,返回修复后(校正校验位)的单号 若前 8 位格式错误(非 2 字母 + 6 数字)或


chrome-devtools-mcp windows 环境安装
monkeySix2025/10/2

问题: Could not find Google Chrome executable for channel 'stable' at 'C:\Program Files\Google\Chrome\Application\chrome.exe'. 解决方案: "chrome-devtools": { "command": "npx", "args": [ "chrome-devtools-mcp@latest", "-


C语言趣味小游戏----猜数字小游戏
雨落在了我的手上2025/10/2

一:猜数字游戏的雏形 部分代码如下,这下面的代码只是一个初步的框架,还不能正式的进行猜数字游戏,因为我还没有将猜数字的代码写进去,这里我先给大家解释一下是下面代码的大致意思 首先呢这个猜数字游戏我们要运用do while循环以及switch语句来初步的进行我们的猜数字游戏,当然我们可以不用do while循环,可以用while循环还有就是 for循环,但是这里我推荐大家用do while循环,因为会更加的好理解以及使用 现在我分别拆开代码来和大家一一介绍 int main() { in


Python 的内置函数 aiter
IMPYLH2025/10/2

Python 内建函数列表 > Python 的内置函数 aiter 你是否曾经在异步编程中处理过异步迭代器(Async Iterators)?是否对 async for 循环背后的机制感到好奇?那么,aiter() 就是 Python 提供的一个关键工具,它允许我们更灵活地处理异步可迭代对象(Async Iterables)。 aiter 的函数原型如下: def aiter(async_iterable): ''' 获取异步可迭代对象的迭代器 :param a

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0