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

JavaScript变量的作用域介绍

JavaScript变量的作用域介绍

JavaScript 变量的作用域决定了变量在代码中的可访问性。

var 是 JavaScript 中最早用于声明变量的关键字,它函数作用域或全局作用域。

let 关键字,具有块级作用域、全局作用域。

const关键字,具有块级作用域、全局作用域。

var、let 和 const 关键字对比表

特性

var

let

const

作用域

函数作用域或全局作用域

块级作用域、全局作用域

块级作用域、全局作用域

变量提升

提升到作用域顶部,初始化为 undefined

声明前访问会报错,因存在“暂时性死区”(Temporal Dead Zone)

声明前访问会报错,因存在“暂时性死区”(Temporal Dead Zone)

重复声明

合法,后续声明覆盖前一个值

不合法,抛出 SyntaxError

不合法,抛出 SyntaxError

是否可变

可变

可变

不可变(引用不可变,但内容可变)【注】

是否成为全局对象属性

是(在全局上下文中)

【注:const 声明的变量不可重新赋值,但若为对象/数组,其内容可修改。示例:
const arr = [1, 2];
arr.push(3); // 允许
arr = [4, 5]; // 报错 】

下面展开介绍

1. 作用域类型

全局作用域(Global Scope

  • 定义:在函数外声明的变量。
  • 特点
    • 任何地方都可访问,包括函数内部。过度使用全局变量可能导致命名冲突和代码难以维护。
    • 在浏览器环境中,全局变量通常与 window 对象关联。
    • 浏览器环境中,var 声明的全局变量会成为 window 对象(在浏览器环境中)的属性,let 和 const 不会。
  • 注意

避免隐式全局变量,始终显式声明变量。

在非严格模式下,未使用 var、let 或 const 关键字声明的变量会被隐式提升为全局变量。

在严格模式下,未声明的变量会导致 ReferenceError,因此不会创建隐式全局变量。

var:在全局上下文中声明的变量会成为全局对象的属性,具有全局作用域。

let 和 const:在全局上下文中声明的变量是全局变量,但不会成为全局对象的属性; let 或 const 关键字还可以声明块级作用域(见后面)。

  • 全局变量的创建方式: 

1)隐式全局变量:

myGlobalVar = "Hello, World!"; // 隐式全局变量
console.log(myGlobalVar); // 输出 "Hello, World!"
console.log(window.myGlobalVar); // 输出 "Hello, World!",因为它是 window 的属性

2)显式全局变量:

使用window对象可以明确地创建全局变量。

window.myExplicitGlobalVar = "Hello, again!"; // 显式全局变量
console.log(window.myExplicitGlobalVar); // 输出 "Hello, again!"
console.log(myExplicitGlobalVar); // 输出 "Hello, again!",直接访问也可以。

3) 使用 var、let 或 const 关键字声明全局变量

//使用 var 声明全局变量
var globalVar = "Hello";
console.log(globalVar); // 输出: Hello
console.log(window.globalVar); // 输出: Hello,因为它是 window 对象的属性

//使用 let 声明全局变量
let globalLetVar = "Hello";
console.log(globalLetVar); // 输出: Hello
console.log(window.globalLetVar); // 输出: undefined(不是 window 的属性)

//使用 const 声明全局变量
const globalConstVar = "Hello";
console.log(globalConstVar); // 输出: Hello
console.log(window.globalConstVar); // 输出: undefined(不是 window 的属性)

注意,虽然全局变量在某些情况下是必要的,但为了防止命名冲突和提高代码质量,现代编程实践中通常不鼓励过度依赖全局变量。

函数作用域(Function Scope

  • 定义:在函数内部用var声明的变量和函数参数,作用范围为整个函数。
  • 特点
    • 在函数内任何位置(包括嵌套代码块)可访问。
  • 示例
function func() {
  if (true) {
    var innerVar = '内部变量'; // 属于函数作用域
  }
  console.log(innerVar); // '内部变量'(正常访问)
}
func();

 此例同时说明,var 声明的变量没有块级作用域,即使在块(如 if、for、while 等)中声明,变量仍然属于包含它的函数或全局作用域。

var 声明的变量会被提升(hoisting)到当前作用域的顶部,但赋值不会被提升。这意味着变量在声明之前可以访问,但值为 undefined。例如:

console.log(hoistedVar); // 输出: undefined
var hoistedVar = 'I am hoisted';

块级作用域(ES6+

  • 定义:由 {} 包围的代码块(如 if、for),使用 let 或 const 声明。
  • 特点
    • 变量仅在块内有效。
    • 避免循环变量泄露等问题。
  • 示例
if (true) {
  let blockVar = '块内变量';
  const PI = 3.14;
}
console.log(blockVar); // 报错:blockVar未定义

此例说明,let 和 const 不会被提升,存在“暂时性死区”(Temporal Dead Zone),即声明前访问会报错。

注意

    •      let 和 const 声明的变量仅在 声明它们的代码块内有效。

    •      如果在函数体的最外层(不嵌套在任何代码块中)声明,则作用域为 整个函数体(因为函数体本身是一个块级作用域)。

    •      如果在函数内的嵌套代码块中声明(如 if 内部),则作用域仅限该代码块。

var、let和const的区别

  • var
    • 具有函数作用域(在函数内部声明的变量只在函数内部有效)。
    • 如果在全局作用域中声明,则具有全局作用域。
    • 存在变量提升(hoisting),但未初始化的变量会返回undefined。
    • 可以重复声明同一个变量。
  • let和const
    • 具有块级作用域(在代码块内部声明的变量只在代码块内部有效)。
    • 不会被提升到块的顶部。
    • 不允许重复声明同一个变量。
    • let声明的变量可以重新赋值,而const声明的变量不能重新赋值(具有只读性,且必须在声明时立即赋值,否则报错))。

模块作用域(Module scope

模块作用域的定义

  • 定义:每个 ES6 模块(以 .mjs 扩展名或 <script type="module"> 标签引入)拥有独立的作用域,模块内声明的变量、函数、类等默认仅在模块内可见,不会污染全局作用域。
  • 核心规则
    • 模块内的顶级变量(var、let、const)不会自动成为全局对象的属性(如 window 或 global)。
    • 模块间需要通过 export 导出和 import 导入来共享变量或函数。

浏览器环境,需使用 <script type="module"> 标签加载模块。例如

<script type="module" src="app.mjs"></script>

模块作用域的特点

(1) 默认隔离性

// moduleA.mjs
let privateVar = "模块A的私有变量"; // 仅模块A可见
export const publicVar = "模块A的公开变量";

// moduleB.mjs
import { publicVar } from './moduleA.mjs';
console.log(publicVar); // "模块A的公开变量"
console.log(privateVar); // 报错:privateVar未定义

(2) 不会污染全局对象

// 模块内声明变量
var moduleVar = "模块变量";
console.log(window.moduleVar); // undefined(浏览器环境)

(3) 隐式严格模式

ES6 模块默认启用严格模式,无需显式添加 'use strict'。

// 以下代码在模块中直接报错,非模块脚本可能不会
undeclaredVar = 10; // 报错:未声明变量

2.作用域链与闭包

  • 作用域链(Scope Chain:函数在定义时确定作用域链,逐级向上查找变量。
  • 闭包(Closures:函数保留对外部作用域的引用,即使外部函数已执行完毕。
    • 内部函数可以访问外部函数的变量。
    • 外部函数执行完毕后,其作用域不会被销毁,而是被内部函数引用。

作用域链示例

let globalVar = "global";

function outerFunction() {
    let outerVar = "outer";

    function innerFunction() {
        let innerVar = "inner";
        console.log(innerVar); // 输出: inner
        console.log(outerVar); // 输出: outer
        console.log(globalVar); // 输出: global
    }

    innerFunction();
}

outerFunction();

当访问一个变量时,JavaScript会按照作用域链的顺序查找变量。作用域链是从当前作用域开始,逐级向上查找,直到全局作用域。

闭包示例

function outerFunction() {
    let outerVar = "I am outer";

    function innerFunction() {
        console.log(outerVar); // 访问外部变量
    }

    return innerFunction;
}

let myClosure = outerFunction();
myClosure(); // 输出: I am outer

闭包是指函数可以访问其外部作用域的变量,即使外部函数已经执行完毕。

附录1、严格模式(使用'use strict'

  • 在严格模式下,未声明的变量会导致ReferenceError,从而避免全局变量污染。
  • 严格模式是ES5引入的一种特殊的运行模式,使代码在更严格的条件下运行。
  • 通过在代码开头添加 "use strict" 声明来启用。
  • 可以应用于整个脚本或单个函数。

启用方式

// 整个脚本使用严格模式
"use strict";
// 后续代码...

// 或在函数内使用
function myFunction() {
    "use strict";
    // 函数代码...
}
 

示例:非严格模式和严格模式下未声明变量直接赋值的行为差异。

假设我们有以下代码片段:

function myFunction() {
    x = 10; // 未声明的变量 x
    console.log(x); // 输出 x 的值
}

myFunction();
console.log(x); // 在全局作用域中访问 x

1)非严格模式下的行为
在非严格模式下,如果直接给未声明的变量赋值,JavaScript 会自动将该变量提升为全局变量。
function myFunction() {
    x = 10; // 未声明的变量x
    console.log(x); // 输出 10
}

myFunction();
console.log(x); // 输出 10
解释:
在myFunction函数中,变量x没有被声明(没有使用var、let或const),但直接赋值为10。
非严格模式下,JavaScript 会自动将x提升为全局变量。因此,x在函数内部和全局作用域中都可以被访问。
这种行为可能导致意外的全局变量污染。


2)严格模式下的行为
在严格模式下,未声明的变量直接赋值会抛出ReferenceError。
'use strict'; // 启用严格模式

function myFunction() {
    x = 10; // 未声明的变量 x,报错:未声明变量
    console.log(x); // 抛出 ReferenceError : x is not defined
}

myFunction();
console.log(x); // 这行代码不会被执行
解释:
在严格模式下,JavaScript 不允许直接给未声明的变量赋值。
如果尝试给未声明的变量赋值,JavaScript 会抛出ReferenceError,提示变量未定义。
这种行为可以防止意外创建全局变量,减少潜在的错误和命名冲突。
 

关于 JavaScript 严格模式的官方文档可见https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode

附录2、可变(Mutable)和不可变(Immutable

“可变”(Mutable)和“不可变”(Immutable)是描述变量或数据是否可以被修改的术语。

1)可变(Mutable

如果一个变量或数据结构的内容可以被修改,那么它就是可变的。这意味着你可以直接改变变量的值或数据结构中的某些部分,而不会创建新的对象。

示例

// 可变的变量

let number = 10;

number = 20; // 修改变量的值,number 现在是 20

// 可变的对象

let obj = { name: "Alice" };

obj.name = "Bob"; // 修改对象的属性,obj 现在是 { name: "Bob" }

obj.age = 25; // 添加新的属性,obj 现在是 { name: "Bob", age: 25 }

在上面的例子中:

number 是一个可变的变量,因为它的值可以从 10 改为 20。

obj 是一个可变的对象,因为它的属性可以被修改或添加。

2)不可变(Immutable

如果一个变量或数据结构的内容不能被修改,那么它就是不可变的。这意味着一旦创建,它的值或内容就无法被改变。如果需要“修改”,通常会创建一个新的对象或变量。

示例

// 不可变的变量(使用 const 声明)

const PI = 3.14;

PI = 3.14159; // 抛出 TypeError: Assignment to constant variable.

在上面的例子中:

PI 是一个不可变的变量,因为它被 const 声明,不能被重新赋值。

不可变对象

虽然 JavaScript 中没有原生的不可变对象,但可以通过一些技术手段(如 Object.freeze())使对象的结构和属性不可变。

const obj = Object.freeze({ name: "Alice" });

obj.name = "Bob"; // 不会报错,但不会生效,obj 仍然是 { name: "Alice" }

obj.age = 25; // 不会报错,但不会生效,obj 仍然是 { name: "Alice" }

obj = {}; // 报错

在上面的例子中:

obj 是一个不可变的对象,因为通过 Object.freeze() 方法,它的属性无法被修改或添加。

3. 可变与不可变的区别

  • 可变数据
    • 可以直接修改。
    • 修改操作通常会影响原始数据。
    • 在某些情况下可能导致意外的副作用(如在函数中修改传入的对象)。
  • 不可变数据
    • 不能直接修改。
    • 修改操作会创建一个新的对象或数据结构。
    • 更安全,避免了意外修改导致的错误。
    • 常用于函数式编程,因为函数式编程强调“无副作用”的函数。

小结

  • 可变(Mutable:可以被修改。
  • 不可变(Immutable:不能被修改。
  • 在 JavaScript 中,let 声明的变量是可变的,而 const 声明的变量是不可变的(但对象或数组的内容可以被修改)。
  • 不可变性有助于提高代码的安全性和可维护性,尤其是在复杂的应用中。

附录3、模块及在浏览器环境测试运行

假设有如下两个模块math.mjs和app.mjs

// math.mjs模块文件
export const PI = 3.1415;
export function sum(a, b) { return a + b; }
// app.mjs入口模块文件
import { PI, sum } from './math.mjs';
console.log(PI); // 3.1415
console.log(sum(2, 3)); // 5

如何在浏览器环境测试运行?

创建文件目录结构

D:/My-project/

├── index.html

├── app.mjs

└── math.mjs

编写 HTML 入口文件,在 index.html 中通过 <script type="module"> 引入入口模块:

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ES Modules Test</title>
</head>
<body>
  <h1>ES Modules Test</h1>
  <script type="module" src="./app.mjs"></script>
</body>
</html>

特别提示,浏览器出于安全限制,禁止直接通过 file:// 协议加载 ES 模块——不能直接用浏览器打开index.html。你需要通过本地 HTTP 服务器运行代码。需要使用启动本地服务器方式,这里使用 Python 内置服务器,需要你已安装了Python。

a.在cmd中用cd命令(其中/d 参数用于切换到其他盘符),进入项目目录

cd /d D:/my-project

Python的HTTP服务器

b.再在cmd中用如下命令

python -m http.server 8000

参见下图:

c.然后(不要退出Python的HTTP服务器)在浏览器中访问http://127.0.0.1:8000

打开浏览器的开发者工具(按 F12 或右键选择“检查”)。

切换到 Console(控制台) 标签。

如果一切正常,你会看到输出:

3.1415
5

OJK!


http://www.kler.cn/a/556187.html

相关文章:

  • AI 在未来相机领域的应用前景如何?
  • OpenGL(2)基于Qt做OpenGL开发
  • 2024年数学SCI1区TOP:改进海洋捕食者算法MMPA用于UAV路径规划,深度解析+性能实测
  • Linux性能监控工具汇总
  • 怎麼防止爬蟲IP被網站封鎖?
  • Javascript网页设计案例:通过PDFLib实现一款PDF分割工具,分割方式自定义-完整源代码,开箱即用
  • 基于 C++ OpenCV 图像灰度化 DLL 在 C# WPF 中的拓展应用
  • Grok 使用指南
  • 清华大学:DeepSeek与AI幻觉(31页PDF)
  • 图数据库Neo4j面试内容整理-路径查询
  • Innovus中快速获取timing path逻辑深度的golden脚本
  • 基于springboot+vue的酒店管理系统的设计与实现
  • 遥感与GIS在滑坡、泥石流风险普查中的实践技术应用
  • Python logger模块
  • 美团一面:说说synchronized的实现原理?
  • 服务器释放screen资源(Detached状态并不会释放资源)
  • 华为动态路由-OSPF-完全末梢区域
  • synchronized锁字符串
  • 店铺矩阵崩塌前夜:跨境多账号运营的3个生死线
  • Prompt Engineering的重要性