首页/新闻资讯/正文详情

JavaScript 函数:作用域、闭包与高阶函数

发布时间:2026/9/24 5:01:37 来源:云帆数科 栏目:资讯中心
JavaScript 函数:作用域、闭包与高阶函数
在上一篇文章中我们了解了 JavaScript 函数的基本概念——如何定义函数、传递参数、返回值以及箭头函数的用法。但会写函数、会调用函数只是入门的第一步。你有没有想过为什么一个函数可以访问在它外面声明的变量为什么一个函数在创建它的外层函数已经执行完之后还能访问那个变量为什么一个函数可以被当作参数传给另一个函数这些行为背后涉及 JavaScript 三个核心概念作用域、闭包和高阶函数。本文带你一步步理解它们并用实例说明它们之间的联系。如果你在手机上调试网页也可以先了解一下如何让 iPhone Safari 在后台打开新标签页方便同时查看代码和文档。一、理解作用域作用域决定了变量在代码中的哪些位置可以被访问。看这个例子const name Dini; function greet() { console.log(name); } greet();greet()函数可以访问name尽管name并不是在函数内部声明的。这是因为name声明在外部作用域中。再看另一个例子function greet() { const message Hello, Dini!; console.log(message); // Hello, Dini! } greet(); console.log(message); // ReferenceError: message is not defined第一个console.log()正常运行因为message是在greet()内部声明的。但第二个会报错因为message只存在于函数的作用域里。全局作用域 └── greet() └── messagemessage在greet()内部可以访问但从外部访问不到。这就是作用域的核心一个变量只能在它被定义的作用域以及嵌套在它内部的作用域中被访问。JavaScript 有以下几种作用域全局作用域脚本模式下所有代码的默认作用域。模块作用域模块模式下代码的作用域。函数作用域由函数创建的作用域。块级作用域由一对花括号代码块创建的作用域。注意代码块只对let和const声明生效对var不生效。{ var x 10; } console.log(x); // 10{ const x 10; } console.log(x); // ReferenceError: x is not defined二、词法作用域与作用域链JavaScript 使用词法作用域也就是说变量的可访问性由代码书写的位置决定而不是由函数调用的位置决定。const name Dini; function outer() { const greeting Hello; function inner() { console.log(greeting); console.log(name); } inner(); } outer();inner()可以同时访问greeting和name。为什么因为 JavaScript 通过作用域链来查找变量。inner() ↓ outer() ↓ 全局作用域当 JavaScript 执行console.log(greeting)时先在inner()内部找greeting没找到再到外层outer()找找到了查找停止。而执行console.log(name)时inner()和outer()里都没有name于是继续往全局作用域找最终在那里找到。这就是作用域链。要记住这个关系只能从内层往外层走。内层函数可以访问外层作用域的变量反过来不行。这个概念非常重要因为它是理解闭包的基础。三、什么是闭包理解了作用域和作用域链之后来看一个初看可能觉得奇怪的现象function createGreeting() { const message Hello, Dini!; return function () { console.log(message); }; } const greet createGreeting(); greet();输出是Hello, Dini!你可能会疑惑createGreeting()已经执行完了greet()为什么还能访问message按常理外层函数执行完后它内部的变量应该被回收才对。这就是闭包在起作用。函数会记住它诞生时的作用域当 JavaScript 创建createGreeting()返回的那个内部函数时这个函数会保持对其创建时所处作用域中所有变量的访问权。greet ↓ function () { console.log(message); } ↓ 记住了对 ↓ message Hello, Dini! 的访问也就是说即使createGreeting()已经执行完毕返回的函数仍然能访问它创建时的环境。这种行为就叫闭包。简单定义闭包是指函数在其外层函数执行完毕后仍然保持对外层词法作用域中变量的访问能力。关键点在于函数记住的不是变量的值的拷贝而是对变量本身的引用。闭包可以保存状态看一个计数器的例子function createCounter() { let count 0; return function () { count; return count; }; } const counter createCounter(); console.log(counter()); // 1 console.log(counter()); // 2 console.log(counter()); // 3count声明在createCounter()内部。执行完const counter createCounter()后createCounter()已经结束了。但每次调用counter()count的值仍然存在并且在之前的基础上累加。这是因为返回的函数形成了对count的闭包在多次调用之间维持了这个变量的状态。闭包创建独立的状态更有意思的是如果创建两个计数器function createCounter() { let count 0; return function () { count; return count; }; } const counterA createCounter(); const counterB createCounter(); console.log(counterA()); // 1 console.log(counterA()); // 2 console.log(counterB()); // 1 console.log(counterB()); // 2虽然两个计数器来自同一个函数但它们各自维护独立的count互不影响。每次调用createCounter()都会创建一个新的词法环境返回的函数闭包在各自的环境上。这就是闭包的强大之处它可以用来创建独立、持久的状态。闭包有什么用闭包不只是 JavaScript 的一个有趣特性它在实际开发中用途广泛在多次函数调用之间保持状态创建函数工厂封装数据创建需要访问外部变量的回调函数处理异步操作举个函数工厂的例子function createMultiplier(multiplier) { return function (number) { return number * multiplier; }; } const double createMultiplier(2); const triple createMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15double记住了multiplier 2triple记住了multiplier 3。同一个工厂函数可以生成不同的函数各自保留自己的状态。这就是闭包的核心思想一个函数可以携带它创建时所处环境的访问权。四、函数是值JavaScript 函数还有一个重要特性函数就是值。这意味着函数可以像其他值一样对待——可以赋值给变量、可以传给另一个函数、也可以从函数中返回。把函数赋值给变量const greet function () { console.log(Hello!); }; greet();这里greet存储的不是函数的执行结果而是对函数本身的引用。所以要加括号greet()才会执行。还可以把同一个函数赋值给另一个变量const greet function () { console.log(Hello!); }; const sayHello greet; sayHello();把函数作为参数传递function greet(name) { console.log(Hello, ${name}!); } function processUser(name, callback) { callback(name); } processUser(Dini, greet);注意我们传的是greet不是greet()。传greet是把函数本身传过去传greet()会立刻执行函数传过去的是它的返回值。这种被传给另一个函数、稍后再被执行的函数通常叫做回调函数。从函数中返回另一个函数function createGreeting(name) { return function () { console.log(Hello, ${name}!); }; } const greetDini createGreeting(Dini); greetDini();这个例子和闭包那一节很像。返回的函数不仅是一个函数值它还闭包在了name变量上。const greetDini createGreeting(Dini); const greetNaufal createGreeting(Naufal); greetDini(); // Hello, Dini! greetNaufal(); // Hello, Naufal!每个返回的函数都能访问自己那份name。函数可以被存储、传递和返回——这就是所谓的一等函数。JavaScript 把函数当作一等公民来对待这也是函数如此强大的原因。五、什么是高阶函数既然函数可以接收另一个函数作为参数或者返回一个函数那么操作其他函数的函数叫什么这就是高阶函数。定义高阶函数是满足以下至少一个条件的函数接收另一个函数作为参数返回一个函数作为结果两者都做。看一个简单例子function processNumber(number, operation) { return operation(number); } function double(number) { return number * 2; } const result processNumber(5, double); console.log(result); // 10processNumber()接收double作为参数然后执行它。它不需要知道operation具体做什么只需要接收函数并调用它。这让函数更加灵活和可复用。高阶函数 vs 回调函数这两个概念紧密相关但描述的是不同角色processNumber()是高阶函数它接收另一个函数double()是回调函数它被传给高阶函数稍后被调用。回调函数是被传过去稍后执行的函数高阶函数是接收或返回其他函数的函数。高阶函数也可以返回函数回到之前createMultiplier的例子function createMultiplier(multiplier) { return function (number) { return number * multiplier; }; } const double createMultiplier(2); const triple createMultiplier(3);createMultiplier()返回了一个新函数所以它也是高阶函数。返回的函数同时闭包在了multiplier上。这就是高阶函数和闭包协同工作的典型例子。高阶函数有什么用高阶函数让代码更灵活、可复用、可组合。我们不需要为每种操作写一个独立的函数而是写一个通用函数在调用时传入具体的行为。function calculate(number, operation) { return operation(number); } const double calculate(10, number number * 2); const square calculate(10, number number ** 2); console.log(double); // 20 console.log(square); // 100实际上你大概率已经用过很多次高阶函数了。比如数组的map()const numbers [1, 2, 3, 4, 5]; const doubled numbers.map(number number * 2);map()接收一个函数作为参数所以它是高阶函数。类似的还有filter()、forEach()、reduce()、find()、some()、every()等。六、数组方法中的高阶函数用同一个数据集来演示const users [ { name: Dini, age: 25, active: true }, { name: Naufal, age: 30, active: false }, { name: Nabil, age: 28, active: true }, ];map()map()通过对原数组的每一项进行变换返回一个新数组。const names users.map(user user.name); console.log(names); // [Dini, Naufal, Nabil]用法规则当你想把每一项都变换成另一种形式时用 map()。filter()filter()筛选出满足条件的项返回新数组。const activeUsers users.filter(user user.active); console.log(activeUsers);回调返回true的项被保留返回false的被剔除。用法规则当你想从数组中挑选出符合条件的若干项时用 filter()。forEach()forEach()对每一项执行一次函数但不返回新数组。users.forEach(user { console.log(user.name); }); // Dini // Naufal // Nabil注意forEach()返回undefined。如果你需要得到一个新数组应该用map()。用法规则当你只想对每一项执行某个操作、不需要返回新数组时用 forEach()。reduce()reduce()把数组的多项合并成一个最终结果。const totalAge users.reduce((total, user) { return total user.age; }, 0); console.log(totalAge); // 83第二个参数0是累加器的初始值。回调接收两个参数total当前累加值和user当前项。reduce()的结果不一定是数字也可以是对象const usersByName users.reduce((result, user) { result[user.name] user; return result; }, {}); console.log(usersByName);用法规则当你想把整个数组归约成一个值或一个对象时用 reduce()。总结对比方法作用返回值map()变换每一项新数组filter()筛选符合条件的项新数组forEach()对每一项执行操作undefinedreduce()归约为一个结果单个累加结果这些方法虽然用途不同但模式是一样的数组方法负责怎么遍历回调函数负责对每一项做什么。这就是高阶函数的威力——我们不需要自己写循环逻辑只需要描述想要的行为。比如不使用filter()时我们要这样写const activeUsers []; for (const user of users) { if (user.active) { activeUsers.push(user); } }使用filter()后const activeUsers users.filter(user user.active);第二版更关注我们要什么活跃用户而不是怎么遍历。这就是现代 JavaScript 中高阶函数如此常用的原因。七、总结本文梳理了 JavaScript 函数相关的几个核心概念作用域决定变量在哪里可以被访问。词法作用域与作用域链决定 JavaScript 从哪里开始查找变量。闭包让函数在外部作用域结束后仍能访问其中的变量。函数是值函数可以被存储、传递和返回。高阶函数接收或返回其他函数的函数如map()、filter()、reduce()。理解这些概念之间的联系是写出清晰、优雅 JavaScript 代码的基础。写代码时遇到环境问题可以参考Windows 网络相关设置以及其他实用教程。相关阅读如何手动和自动把 iPhone 备份到 iCloud如何在 Safari 浏览器中允许或拦截弹窗如何关闭查找我的 iPhone

相关推荐

高性能 Go JSON 库 json-iterator/go:vcluster 中的 100% 兼容序列化方案与源码解析
高性能 Go JSON 库 json-iterator/go:vcluster 中的 100% 兼容序列化方案与源码解析

云原生集群管理虚拟化多集群 【免费下载链接】vcluster vCluster creates tenant clusters: fully isolated environments delivered as managed Kubernetes, or as the foundation for Slurm, Ray, Run:ai and inference clusters. Each gets its own API server, CRDs and RB… · 2026/9/24 5:01:37

Akka Streams Source.queue 操作符全解析:同步 BoundedSourceQueue 与异步 SourceQueue 的选型与背压实践
Akka Streams Source.queue 操作符全解析:同步 BoundedSourceQueue 与异步 SourceQueue 的选型与背压实践

后端并发编程异步编程 【免费下载链接】akka-core A platform to build and run apps that are elastic, agile, and resilient. SDK, libraries, and hosted environments. 项目地址: https://gitcode.com/gh_mirrors/ak/akka-core 点击查看 免费下载 Source.queu… · 2026/9/24 5:01:30

洗眉洗纹身报的是单次价格还是后续总费用
洗眉洗纹身报的是单次价格还是后续总费用

问洗眉、洗纹身多少钱,最容易少问的其实是“这个钱买了什么”。一次服务的报价、以后可能发生的支出、已经预付却还没使用的服务,是三件不同的事。先分清自己属于哪一种,再讨论预算或暂停,金额才不会越算越乱。在长沙考虑曼肤医疗… · 2026/9/24 5:01:30

I2C、SPI、UART、I2S总线选型指南:从原理到实战
I2C、SPI、UART、I2S总线选型指南:从原理到实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 9:10:33

AI 生成的主图,为什么还不能直接上架
AI 生成的主图,为什么还不能直接上架

一张新主图生成出来,商品看着更亮,背景也更干净。运营同事准备点上架,设计师先放大看了三处:包装上的字有没有变形、颜色是否还像实物、配件是不是被多画了一件。 AI 让制作变快,也把检查的重点换了位置。 视觉工具擅长… · 2026/9/24 9:10:14

Apache Arrow Swift 入门指南:内存布局、数组类型体系与测试数据生成
Apache Arrow Swift 入门指南:内存布局、数组类型体系与测试数据生成

数据工程大数据序列化数据分析 【免费下载链接】arrow Apache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing 项目地址: https://gitcode.com/gh_mirrors/arrow13/arrow 点击查看 免费下载 Apache Arrow 是一个面… · 2026/9/24 9:10:08

Mosquitto 0.6.1 发布:持久化数据库自动升级缺陷修复与 Release Candidate 流程的引入
Mosquitto 0.6.1 发布:持久化数据库自动升级缺陷修复与 Release Candidate 流程的引入

后端消息队列消息路由 【免费下载链接】mosquitto Eclipse Mosquitto - An open source MQTT broker 项目地址: https://gitcode.com/gh_mirrors/mos/mosquitto 点击查看 免费下载 Mosquitto 0.6.1 是一个针对 0.6.0 的紧急修复版本:它修复了一个在 0.6… · 2026/9/24 9:10:01

STM32F103驱动无FIFO版OV7670摄像头:从接线到图像显示全攻略
STM32F103驱动无FIFO版OV7670摄像头:从接线到图像显示全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 9:09:49

入职GEO公司一周,6张表,帮客户跑通AI获客
入职GEO公司一周,6张表,帮客户跑通AI获客

「我们公司没什么可写的。」这话我听过太多回。说这话的人,多半一个人扛着一摊生意,公司不大,三五个人,墙上挂着自己找人做的牌匾,日子过得不上不下。他往你对面一坐,很诚恳地看着你,说没故事&a… · 2026/9/24 9:09:49

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码