0

0

详解JavaScript作用域和闭包

小云云

小云云

发布时间:2017-12-05 10:40:31

|

2054人浏览过

|

来源于php中文网

原创

作为一名程序员,作用域和闭包在javascript里非常重要。也许在最初的时候我们很难理解什么是作用域和闭包,在javascript中怎么用呢?本文我们将详细介绍javascript作用域和闭包 。

作用域

JavaScript的作用域限定了你可以访问哪些变量。有两种作用域:全局作用域,局部作用域。

全局作用域

在所有函数声明或者大括号之外定义的变量,都在 全局作用域 里。

不过这个规则只在浏览器中运行的JavaScript里有效。如果你在Node.js里,那么全局作用域里的变量就不一样了,不过这篇文章不讨论Node.js。

立即学习Java免费学习笔记(深入)”;

const globalVariable = 'some value'`

 

Insou AI
Insou AI

Insou AI 是一款强大的人工智能助手,旨在帮助你轻松创建引人入胜的内容和令人印象深刻的演示。

下载

一旦你声明了一个全局变量,那么你在任何地方都可以使用它,包括函数内部。

const hello = 'Hello CSS-Tricks Reader!'

function sayHello () {
  console.log(hello)
}

console.log(hello) // 'Hello CSS-Tricks Reader!'
sayHello() // 'Hello CSS-Tricks Reader!'

 

尽管你可以在全局作用域定义变量,但我们并不推荐这样做。因为可能会引起命名冲突,两个或更多的变量使用相同的变量名。如果你在定义变量时使用了 const 或者 let ,那么在命名有冲突时,你就会收到错误提示。这是不可取的。

// Don't do this!
let thing = 'something'
let thing = 'something else' // Error, thing has already been declared

 

如果你定义变量时使用的是 var ,那第二次定义会覆盖第一次定义。这也会让代码更难调试,也是不可取的。

// Don't do this!
var thing = 'something'
var thing = 'something else' // perhaps somewhere totally different in your code
console.log(thing) // 'something else'

 

所以,你应该尽量使用局部变量,而不是全局变量

局部作用域

在你代码某一个具体范围内使用的变量都可以在局部作用域内定义。这就是 局部变量

JavaScript里有两种局部作用域:函数作用域和块级作用域。

我们从函数作用域开始。

函数作用域

当你在函数里定义一个变量时,它在函数内任何地方都可以使用。在函数之外,你就无法访问它了。

比如下面这个例子,在 sayHello 函数内的 hello 变量:

function sayHello () {
  const hello = 'Hello CSS-Tricks Reader!'
  console.log(hello)
}

sayHello() // 'Hello CSS-Tricks Reader!'
console.log(hello) // Error, hello is not defined

 

块级作用域

你在使用大括号时,声明了一个 const 或者 let 的变量时,你就只能在大括号内部使用这一变量。

在下例中, hello 只能在大括号内使用。

{
  const hello = 'Hello CSS-Tricks Reader!'
  console.log(hello) // 'Hello CSS-Tricks Reader!'
}

console.log(hello) // Error, hello is not defined

 

块级作用域是函数作用域的子集,因为函数是需要用大括号定义的,(除非你明确使用return语句和箭头函数)。

函数提升和作用域

当使用function定义时,这个函数都会被提升到当前作用域的顶部。因此,下面的代码是等效的:

// This is the same as the one below
sayHello()
function sayHello () {
  console.log('Hello CSS-Tricks Reader!')
}

// This is the same as the code above
function sayHello () {
  console.log('Hello CSS-Tricks Reader!')
}
sayHello()

 

使用函数表达式定义时,函数就不会被提升到变量作用域的顶部。

sayHello() // Error, sayHello is not defined
const sayHello = function () {
  console.log(aFunction)
}

 

因为这里有两个变量,函数提升可能会导致混乱,因此就不会生效。所以一定要在使用函数之前定义函数。

函数不能访问其他函数的作用域

在分别定义的不同的函数时,虽然可以在一个函数里调用一个函数,但一个函数依然不能访问其他函数的作用域内部。

下面这例, second 就不能访问 firstFunctionVariable 这一变量。

function first () {
  const firstFunctionVariable = `I'm part of first`
}

function second () {
  first()
  console.log(firstFunctionVariable) // Error, firstFunctionVariable is not defined
}

 

嵌套作用域

如果在函数内部又定义了函数,那么内层函数可以访问外层函数的变量,但反过来则不行。这样的效果就是词法作用域。

外层函数并不能访问内部函数的变量。

function outerFunction () {
  const outer = `I'm the outer function!`

  function innerFunction() {
    const inner = `I'm the inner function!`
    console.log(outer) // I'm the outer function!
  }

  console.log(inner) // Error, inner is not defined
}

 

如果把作用域的机制可视化,你可以想象有一个双向镜(单面透视玻璃)。你能从里面看到外面,但是外面的人不能看到你。

详解JavaScript作用域和闭包

函数作用域就像是双向镜一样。你可以从里面向外看,但是外面看不到你。

嵌套的作用域也是相似的机制,只是相当于有更多的双向镜。

详解JavaScript作用域和闭包

多层函数就意味着多个双向镜。

理解前面关于作用域的部分,你就能理解闭包是什么了。

闭包

你在一个函数内新建另一个函数时,就相当于创建了一个闭包。内层函数就是闭包。通常情况下,为了能够使得外部函数的内部变量可以访问,一般都会返回这个闭包。

function outerFunction () {
  const outer = `I see the outer variable!`

  function innerFunction() {
    console.log(outer)
  }

  return innerFunction
}

outerFunction()() // I see the outer variable!

 

因为内部函数是返回值,因此你可以简化函数声明的部分:

function outerFunction () {
  const outer = `I see the outer variable!`

  return function innerFunction() {
    console.log(outer)
  }
}

outerFunction()() // I see the outer variable!

 

因为闭包可以访问外层函数的变量,因此他们通常有两种用途:

  1. 减少副作用

  2. 创建私有变量

使用闭包控制副作用

当你在函数返回值时执行某些操作时,通常会发生一些副作用。副作用在很多情况下都会发生,比如Ajax调用,超时处理,或者哪怕是 console.log 的输出语句:

function (x) {
  console.log('A console.log is a side effect!')
}

 

当你使用闭包来控制副作用时,你实际上是需要考虑哪些可能会混淆代码工作流程的部分,比如Ajax或者超时。

要把事情说清楚,还是看例子比较方便:

比如说你要给为你朋友庆生,做一个蛋糕。做这个蛋糕可能花1秒钟的时间,所以你写了一个函数记录在一秒钟以后,记录做完蛋糕这件事。

为了让代码简短易读,我使用了ES6的箭头函数:

function makeCake() {
  setTimeout(_ => console.log(`Made a cake`, 1000)
  )
}

 

如你所见,做蛋糕带来了一个副作用:一次延时。

更进一步,比如说你想让你的朋友能选择蛋糕的口味。那么你就给做蛋糕 makeCake 这个函数加了一个参数。

function makeCake(flavor) {
  setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
}

 

因此当你调用这个函数时,一秒后这个新口味的蛋糕就做好了。

makeCake('banana')
// Made a banana cake!

 

但这里的问题是,你并不想立刻知道蛋糕的味道。你只需要知道时间到了,蛋糕做好了就行。

要解决这个问题,你可以写一个 prepareCake 的功能,保存蛋糕的口味。然后,在返回在内部调用 prepareCake 的闭包 makeCake 。

从这里开始,你就可以在你需要的时调用,蛋糕也会在一秒后立刻做好。

function prepareCake (flavor) {
  return function () {
    setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
  }
}

const makeCakeLater = prepareCake('banana')

// And later in your code...
makeCakeLater()
// Made a banana cake!

 

这就是使用闭包减少副作用:你可以创建一个任你驱使的内层闭包。

私有变量和闭包

前面已经说过,函数内的变量,在函数外部是不能访问的既然不能访问,那么它们就可以称作私有变量。

然而,有时候你确实是需要访问私有变量的。这时候就需要闭包的帮助了。

function secret (secretCode) {
  return {
    saySecretCode () {
      console.log(secretCode)
    }
  }
}

const theSecret = secret('CSS Tricks is amazing')
theSecret.saySecretCode()
// 'CSS Tricks is amazing'

 

这个例子里的 saySecretCode 函数,就在原函数外暴露了 secretCode 这一变量。因此,它也被成为特权函数。

使用DevTools调试

Chrome和Firefox的开发者工具都使我们能很方便的调试在当前作用域内可以访问的各种变量一般有两种方法。

第一种方法是在代码里使用 debugger 关键词。这能让浏览器里运行的JavaScript的暂停,以便调试。

下面是 prepareCake 的例子:

function prepareCake (flavor) {
  // Adding debugger
  debugger
  return function () {
    setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
  }
}

const makeCakeLater = prepareCake('banana')

 

打开Chrome的开发者工具,定位到Source页下(或者是Firefox的Debugger页),你就能看到可以访问的变量了。

详解JavaScript作用域和闭包

使用debugger调试 prepareCake 的作用域。

你也可以把 debugger 关键词放在闭包内部。注意对比变量的作用域:

function prepareCake (flavor) {
  return function () {
    // Adding debugger
    debugger
    setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
  }
}

const makeCakeLater = prepareCake('banana')

 

详解JavaScript作用域和闭包

调试闭包内部作用域

第二种方式是直接在代码相应位置加断点,点击对应的行数就可以了。

详解JavaScript作用域和闭包

通过断点调试作用域

闭包和作用域并不是那么难懂。一旦你使用双向镜的思维去理解,它们就非常简单了。当你在函数里声明一个变量时,你只能在函数内访问。这些变量的作用域就被限制在函数里了。如果你在一个函数内又定义了内部函数,那么这个内部函数就被称作闭包。它仍可以访问外部函数的作用域。希望本文能帮助到大家。

相关推荐:

JS的解析顺序和作用域以及严格模式的简单介绍

javascript中关于作用于作用域链的详解

JavaScript作用域与闭包的分析讲解

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

49

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

89

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

276

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

59

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

99

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

105

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

230

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

619

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

173

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号