JavaScript变量的作用域介绍

发布于:2025-02-22 ⋅ 阅读:(11) ⋅ 点赞:(0)

JavaScript变量的作用域介绍

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

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

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

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

var、let 和 const 关键字对比表

特性

var

let

const

作用域

函数作用域或全局作用域

块级作用域、全局作用域

块级作用域、全局作用域

变量提升

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

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

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

重复声明

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

不合法,抛出 SyntaxError

不合法,抛出 SyntaxError

是否可变

可变

可变

不可变(引用不可变,但内容可变)

是否成为全局对象属性

是(在全局上下文中)

下面展开介绍

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声明的变量不能重新赋值(具有只读性,且必须在声明时立即赋值,否则报错))。

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())使对象的结构和属性不可变。

JavaScript复制

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

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

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

在上面的例子中:

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

3. 可变与不可变的区别

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

小结

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


网站公告

今日签到

点亮在社区的每一天
去签到