简单理解var、let、const

499 阅读6分钟

ECMAScript 6 (ES6) 简介

ECMAScript 6(通常称为 ES6 或 ES2015),是 JavaScript 语言的一个重要版本,在2015年发布。它引入了许多新的语法特性,以简化代码编写并提高代码质量。其中最显著的变化之一就是对变量声明方式的改进:新增了letconst关键字,用来替代旧版JavaScript中唯一的变量声明方式var

var 关键字

在 ES6 之前,JavaScript 中的变量主要是通过var关键字来声明的。var声明的变量具有函数作用域或全局作用域,并且存在变量提升的现象,这意味着无论变量是在函数内部的哪个位置声明的,它们都会被视为在函数的顶部被声明。并且,var没有块级作用域的概念,即使在一个if语句或for循环中声明,它的作用域仍然会延伸到整个函数或全局环境。

作用域

{
    var blockScoped = "I'm block scoped"; 
}
function exampleFunction() {
    var functionScoped = "I'm function scoped";
    
    if (true) {
        var innerVariable = "This is also function scoped!";
    }
    
    console.log(blockScoped);  // "I'm block scoped"
    console.log(innerVariable); // "This is also function scoped!"
}


exampleFunction();
console.log(functionScoped); // functionScoped is not defined
console.log(innerVariable); // innerVariable is not defined

image.png

blockScoped延伸到了全局环境,innerVariable延伸到了整个函数,而functionScoped的作用域只在函数内,外部访问不到,所以innerVariablefunctionScoped都是局部变量

变量提升

当使用var声明变量时,JavaScript 引擎会在代码执行之前先处理所有的变量声明,这被称为“变量提升”。这意味着无论你在函数或全局作用域的哪个位置声明了变量,它都会被视为已经被声明在该作用域的顶部。然而,只有声明会被提升,并且被初始化为undefined,但是赋值不会被提升。

console.log(x); // 输出: undefined
var x = 10;

function exampleFunction() {
    console.log(y); // 输出: undefined
    var y = 20;
}

exampleFunction();

image.png

在这个例子中,尽管x是在console.log之后声明的,但是由于变量提升,x已经在全局作用域中被声明了,因此输出的是undefined而不是抛出错误。同样的情况也发生在exampleFunction内部的y变量上。

重复声明

var 允许在一个作用域内多次声明同一个变量名。这种行为不会导致错误,但可能会引发意外的行为,因为它实际上只是覆盖了之前的声明,并且所有这些声明都会被提升到作用域的顶部。

  1. 全局作用域中的重复声明
var x = 10;
console.log(x); // 输出: 10

var x = 20; // 重复声明
console.log(x); // 输出: 20

在这个例子中,x 被两次用 var 声明,第二次声明并不会报错,而是简单地覆盖了第一次声明的值。这表明即使有重复声明,也只会保留最后一次赋值。

2.函数作用域中的重复声明

function exampleFunction() {
    var y = 10;
    console.log(y); // 输出: 10

    if (true) {
        var y = 20; // 在函数内部重复声明
        console.log(y); // 输出: 20
    }

    console.log(y); // 输出: 20
}

exampleFunction();

这里,在if语句块内再次声明了变量y。由于var具有函数作用域而不是块级作用域,因此这个新的声明实际上是覆盖了之前在同一函数内的声明,结果是整个函数体内y的值都变成了20。

3.块级作用域内的重复声明

{
    var z = 10;
    console.log(z); // 输出: 10

    if (true) {
        var z = 20; // 再次声明,但由于 var 没有块级作用域,这会覆盖外部的 z
        console.log(z); // 输出: 20
    }

    console.log(z); // 输出: 20
}

在这个例子中,尽管z是在if语句块内重新声明的,但是因为var没有块级作用域的概念,所以它实际上覆盖了外部的z,最终整个代码块内的z都指向20。
注意:不同作用域之间不存在重复声明

if (true) {
    var name = "John";

}
function sayHello() {

    var name = "world";
    console.log("Hello " + name + "!"); // Output: Hello world!
}

sayHello();
console.log(name) // Output: John

在这个例子中,var name = "John";var name = "world"; 不是重复声明,因为它们分别位于不同的作用域中——一个在全局作用域,另一个在 sayHello 函数的作用域。每个作用域内的 name 变量都是独立的,互不影响。

let 关键字

ES6 引入了let关键字,它提供了块级作用域的功能。这意味着使用let声明的变量只在其所在的代码块(如if语句、for循环等)内有效。此外,虽然该声明也会被提升到块的顶部,但是对这个变量的初始化(赋值)不会被提升。

if (true) {
    let y = 10;
    console.log(y); // 10
}
console.log(y); // ReferenceError: y is not defined.

image.png

在声明之前访问这些变量会导致引用错误(ReferenceError),这是因为它们存在一个“暂时性死区”(Temporal Dead Zone, TDZ)。TDZ是指从块的开始到变量声明的那一行之间的区域,在此期间尝试访问变量是非法的。

// 尝试在 let 声明之前访问变量会抛出 ReferenceError
console.log(a); // 抛出 ReferenceError: Cannot access 'a' before initialization
let a = 5;

image.png

const 关键字

const用于声明常量,即一旦赋值就不能再更改其绑定的值。不过需要注意的是,对于对象和数组这样的复杂数据类型,虽然不能改变它们的引用,但可以修改其属性或元素。

const person = { name: 'Alice' };
person.name = 'Bob'; // 这是允许的
person = {}; // TypeError: Assignment to constant variable.

const同样具有块级作用域。这里我们看到,当尝试在块外部访问alsoBlockScoped时,也会得到一个错误。

// 块级作用域内的 const 变量
{
    const alsoBlockScoped = "So am I!";
    console.log(alsoBlockScoped); // 输出: So am I!
}

console.log(alsoBlockScoped); // ReferenceError: alsoBlockScoped is not defined

const也存在暂时性死区,与let类似,留给小伙伴自行体会

变量的作用域

  • 全局作用域:当变量在任何函数外部声明时,它就处于全局作用域,可以在程序的任何地方访问。
  • 局部作用域
    • 函数作用域:由var创建的变量存在于定义它们的函数内部及其嵌套函数中。
    • 块级作用域letconst创建的变量仅限于它们被声明的块(例如if语句、for循环)内部。

总结

ES6 的letconst带来了更为严格和直观的作用域规则,有助于避免一些常见的编程错误。同时,它们的存在鼓励开发者采用更清晰的编码实践,比如优先使用const来声明那些不应该改变的值,从而提高代码的质量和维护性。

本文只是对letvarconst进行了一个简要的介绍。如果有不对的地方欢迎指正。