CSC-NOTESCSC-NOTES
首页
  • 前端笔记

    • Vue
    • React
    • CSS
    • JavaScript
    • TypeScript
  • 服务端笔记

    • NodeJS
  • C/C++

    • C语言
  • 数据库笔记

    • Postgresql
  • 计算机网络

    • 计算机网络基础
    • 计算机网络进阶
  • 操作系统

    • 操作系统基础
    • 操作系统进阶
  • 前端工程化

    • Vue
    • React
  • 后端工程化

    • NodeJS
  • 数据结构
  • 算法
  • 前端面试
  • 后端面试
Github
首页
  • 前端笔记

    • Vue
    • React
    • CSS
    • JavaScript
    • TypeScript
  • 服务端笔记

    • NodeJS
  • C/C++

    • C语言
  • 数据库笔记

    • Postgresql
  • 计算机网络

    • 计算机网络基础
    • 计算机网络进阶
  • 操作系统

    • 操作系统基础
    • 操作系统进阶
  • 前端工程化

    • Vue
    • React
  • 后端工程化

    • NodeJS
  • 数据结构
  • 算法
  • 前端面试
  • 后端面试
Github
  • JavaScript
    • JavaScript基础
      • 1、语法基础
        • 1.1语法
        • 1.2变量
        • 1.3数据类型
        • 1.4操作符
        • 1.5语句
    • JavaScript高级
      • this指向问题
      • 作用域和闭包
      • 变量和函数提升

本文详细介绍了JavaScript中的变量声明方式、作用域规则和生命周期管理。通过实例讲解var、let和const的使用场景和注意事项,帮助开发者编写更安全、可维护的代码。

JavaScript变量

ECMAScript变量是松散的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命令占位符。目前有三个关键字可以声明变量:var/const/let,var在任何ESMCScript版本都可以使用。const/let只能在ECMAScript6及更晚的版本中使用。

1.var声明

要定义变量,可以使用var操作符,后面跟变量名

var message;

这行代码定义了一个名为message的变量,可以用它保存任何类型的值(不初始化的情况下,变量会保存一个特殊值undefined)。

1.1 var声明作用域

使用var操作符定义的变量会变成包含它的函数的局部变量。比如,使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时销毁。

function test(){
  var message = 'hello world';
}
test();
console.log(message); // 报错

这里,message变量是在函数内部使用var定义的。函数是test,调用它会创建这个变量并给它赋值。调用之后变量随即被销毁。不过,在函数内定义变量时省略var操作符,可以创建一个全局变量。

function test() {
  message = 'hello world';
}
test();
console.log(message); // hello world

去掉var操作符之后,message就变成了全局变量。只要调用一个函数test(),就会定义这个变量,并且在函数外部可以访问到。

注意

虽然可以通过剩余 var 操作符定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护,也会造成困惑。在严格模式下,给未声明的变量复制,则会报错 ReferenceError。

1.2 var声明提升

使用var时,下面代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部

function foo () {
  console.log(age);
  var age = 18;
}
foo(); // undefined

之所以不报错,是因为ECMAScript运行时把它等加成如下代码

function foo() {
  var age;
  console.log(age);
  age = 18;
}
foo(); // undefined

这就是所谓的提升,也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用var声明同一个变量也是没有问题。

var age = 8;
var age = 18;
console.log(age); // 18

2.let声明

let跟var的作用差不多,但有着非常重要的区别。最明显的是,let声明的范围是块作用域,而var声明的范围是函数作用域。

function bar() {
if(true) {
  var name = 'Tom';
  console.log(name); // Tom
}
console.log(name) // Tom
if(true) {
  let age = 18;
  console.log(age); // 18
}
console.log(age); // ReferenceError: age没有定义
}

在这里,age变量之所以不能在if块外部被引用,是因为它的作用域仅限于块内部。块作用域是函数作用域的子集,因此适用于var的作用域限制同样适用于let。

let不允许在一个块中出现冗余声明,这样会导致报错。

var name;
var name;

let age;
let age; // SyntaxError age已经被声明过了

JavaScript引擎会记录用于变量声明的标识符以及所在的块作用域,一次嵌套使用相同的标识符不会报错,这是因为在同一个块中没有重复声明

var name = 'Tom';
console.log(name); // Tom
if(true){
  var name = 'Jerry';
  console.log(name); // Jerry
}
let age = 30;
console.log(age); // 30
if(true) {
  let age = 18;
  console.log(age); // 18
}

对于声明冗余报错不会因混用let和var受影响。这两个关键字声明并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。

var name;
let name; // SyntaxError

let age;
var age; // SyntaxError

2.1暂时性死区

let与var的另一个重要的区别,就是let声明的变量不会在作用域中被提升。

// name会提升
console.log(name);
var name = 'Tom';
// age 不会被提升
console.log(age); // ReferenceError
let age = 18;

在解析代码时,JavaScript引擎会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明变量。在let声明之前的执行瞬间被称为暂时性死区,在此阶段引用任何后面才声明的变量都会抛出异常。

2.2全局声明

与var关键字不同,使用let在全局作用域中声明的变量,不会成为window对象的属性(var声明变量则会)。

var name = 'Tom';
console.log(window.name); // Tom
let age = 18;
console.log(window.age); // undefined

不过,let声明仍然是在全局作用域中发生的,变量会在页面的生命周期内存在。

2.3条件声明

在使用var声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量,一旦在同一个块作用域内重复使用let声明同名变量,就会抛出SyntaxError错误。

即使你想使用try/catch或typeof操作符也不能解决,因为条件块中let声明的作用域仅限于改块。

if(typeof name === undefined) {
  let name;
}
// 因为name被限制在if块中,因此下面的赋值形同全局赋值
name = 'Tom';
try{
  console.log(age); // 如果age没有被声明过,则会报错
}catch(error) {
  let age;
}
age = 18;

对于let声明不能依赖条件声明模式,var则可以。

提示

不能使用let进行条件声明是好事,因为条件声明是一种反模式,让程序变得更难理解。

2.4for循环中的let

在let出现之前,for循环定义的迭代变量会渗透到循环体外部。

for(var i = 0; i < 5; i++) {

}
console.log(i); // 5

改成let之后,这个问题就没有了,因为迭代变量的作用域仅限于for循环块内部

for(let i = 0; i < 5; i++) {

}
console.log(i); // ReferenceError i没有定义

在使用var的时候,最常见的问题就是迭代变量的修改

for(var i = 0; i < 5; i++) {
  setTimeout(() => {console.log(i)}, 0);
}

可能会认为输出 0、1、2、3、4

但结果输出的是 5、5、5、5、5

之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时任务时,所有的i都是同一个变量。

在使用let声明迭代变量时,JavaScript引擎会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例。

for(let i = 0; i < 5; i++) {
  setTimeout(() => {console.log(i)}, 0); // 0、1、2、3、4
}

3.const声明

const的行为与let基本相同,唯一一个重要的区别是const声明变量时必须同时初始化变量的值,且在尝试修改这个变量时会导致报错---const声明的是常量。

const age = 18;
age = 20; // TypeError 不能给常量赋值

const声明的限制只适用于它指向的变量引用。如果const变量引用的是一个对象,那么修改这个对象的属性是允许的。

4.声明风格及最佳实践

1.不使用var

有了let/const,大多数开发者会发现不在需要var了。限制自己使用let/const有助于提高代码的质量,因为变量有了明确的作用域、声明的位置、以及不变的值。

2.const优先,let次之

使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此只有在提前知道未来会修改时再使用let。

💬 欢迎评论!请确保您已登录 GitHub。

最近更新: 2025/5/22 11:45
Contributors: chenshicong
Prev
1.1语法
Next
1.3数据类型