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

JavaScript变量的作用域介绍

JavaScript变量的作用域介绍

JavaScript 变量的作用域决定了变量在代码中的可访问性。下面展开介绍。

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!",直接访问也可以。

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

函数作用域(Function Scope

  • 定义:使用在函数内部声明的变量,作用域为整个函数。
  • 特点
    • 在函数内任何位置(包括嵌套代码块)可访问。
  • 示例
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),即声明前访问会报错。

var、let和const的区别

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

2.作用域链与闭包

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

作用域链示例

let globalVar = "global";function outerFunction() {let outerVar = "outer";function innerFunction() {let innerVar = "inner";console.log(innerVar); // 输出: innerconsole.log(outerVar); // 输出: outerconsole.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

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

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

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

启用方式

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

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

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

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

function myFunction() {x = 10; // 未声明的变量 xconsole.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


http://www.mrgr.cn/news/91846.html

相关文章:

  • nodejs运行的坎坷之路
  • 《论系统需求分析方法》写作心得 - 系统分析师
  • 业务流程中的流程管理
  • STL —— 洛谷字符串(string库)入门题(蓝桥杯题目训练)(二)
  • 企业组网IP规划与先关协议分析
  • 第一个CMAKE项目hello cmake
  • k8s故障处理经典案例(Classic Case of k8s Fault Handling)
  • 最新本地部署 DeepSeekR1 蒸馏\满血量化版 + WebOpenUI 完整教程(Ubuntu\Linux系统\Ollama)
  • 用户中心项目教程(九)---前端页面设计测试登录功能
  • YOLOv8与BiFormer注意力机制的融合:提升多场景目标检测性能的研究
  • JavaScript数据类型全解析,怎么区分呢?
  • 【spring】静态代理与动态代理 | AOP面向切面编程
  • stm32单片机个人学习笔记15(I2C通信协议)
  • NLP-RNN-LSTM浅析
  • 自然语言处理NLP 02统计语言模型
  • 【Quest开发】全身跟踪(一)
  • 嵌入式学习(18)---Linux文件编程中的进程
  • LeetCode 热题 100_搜索插入位置(63_35_简单_C++)(二分查找)(”>>“ 与 “/” 对比)
  • 雷龙CS SD NAND(贴片式TF卡)测评体验
  • uni-app开发app时 使用uni.chooseLocation遇到的问题