javascript-var/let/const

loading 2022年10月18日 121次浏览

1. var

1.1 var的声明作用域

使用var操作符定义的变量会成为包含它的函数的局部变量

如此例,调用test()会创建message变量并给它赋值,调用完毕后随即被销毁,因此报错。

function test() { 
 var message = "hi"; // 局部变量
} 
test(); 
console.log(message); // 出错!

如果把var去掉则转化为全局变量,不过不建议在局部作用域中定义全局变量。(在严格模式中,这样给未声明的变量赋值会抛出ReferenceError)

function test() { 
 message = "hi"; // 全局变量
} 
test(); 
console.log(message); // "hi"

1.2 var的变量提升

使用var声明的变量会自动提升到函数作用域顶部

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

等效于

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

同样函数也会被变量提升,和普通属性的区别是函数的变量提升相当于整个剪切过去。

        function foo() {
            console.log(a);

            var a = 1;

            console.log(a);

            function a() { }

            console.log(a);
        }
        foo()

相当于

        function foo() {
            var a;
            function a() { }

            console.log(a); // a()

            a = 1;

            console.log(a); // 1
            console.log(a); // 1
        }
        foo();

而变量提升会导致一些奇怪的问题,如这道经典的打印题:

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

打印结果不是想象中的0~9,而是10个10,为什么?
首先是由于变量提升,导致上述代码实质是这样的:

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

其次是由于setTimeout这个函数会在同步代码执行完后再执行,此处会在别的博客中的事件循环,宏任务微任务篇章中提到。比如加入一句打印验证:

var i = 0;
for ( ; i < 10; i++) {
    console.log("我在for里面噢");
    setTimeout(()=> {
    console.log(i)
  })
}
console.log("我在for循环外面噢");

则结果是:

由此可见,当开始执行setTimeout()中的代码时for循环外面的变量i就已经变成了10,使用console.log(i)从作用域查找到的i值就是10,然后循环十次10。

1.3 var的反复声明

var可以反复多次地声明同一个变量

function foo() { 
 var age = 16; 
 var age = 26; 
 var age = 36; 
 console.log(age); 
} 
foo(); // 36 

1.4 var的全局声明

var在全局作用域中声明的变量会成为window对象的属性

var name = 'Matt'; 
console.log(window.name); // 'Matt' 

2. let

2.1 let的声明作用域

let和var做重要的区别就是:var声明的范围是函数作用域,而let声明的范围是块作用域。

局部作用域 : 仅限于 ‘函数体’ 内部声明的变量
块级作用域 : 一切大括号{} 内部使用let/const声明的变量

if (true) { 
 var name = 'Matt'; 
 console.log(name); // Matt 
} 
console.log(name); // Matt


if (true) { 
 let age = 26; 
 console.log(age); // 26 
} 
console.log(age); // ReferenceError: age 没有定义

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

2.2 let的变量提升

let声明的变量不会在作用域中被提升。在let上方的执行瞬间称为暂时性死区,此时引用下方let的声明的变量会报错

// age 不会被提升
console.log(age); // ReferenceError:age 没有定义
let age = 26; 

因此上方提到的for循环打印问题可以用let解决

        for (let i = 0; i < 10; i++) {
            console.log('我在for里面噢' , i)
            setTimeout(() => {
                console.log(i)
            })
        }
        console.log('我在for外面噢')

也可以通过闭包立即执行函数解决

for (var i = 0 ; i < 10; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i)
    })
  })(i)
}

2.3 let的反复声明

同一个块中不能反复声明,不同的块中可以。

let age = 30; 
console.log(age); // 30 
if (true) { 
 let age = 26; 
 console.log(age); // 26 
} 

2.4 let的全局声明

let在全局作用域中声明的变量不会成为window对象的属性

let age = 26; 
console.log(window.age); // undefined 

3.const

let声明的变量可以改变,值和类型都可以改变;const声明的基本类型常量不可以改变,这意味着,const一旦声明,就必须立即初始化,不能以后再赋值。

但是有例外!
如果用const定义数组或对象这种引用类型的话,里面的值是可以被改变的:

const arr = ["李广程",21];
arr[2] = "深圳";
console.log(arr); // ["李广程", 21, "深圳"]


const object = {name:"李广程",age:21};
object["address"] = "深圳";
console.log(object); // {name: "李广程", age: 21, address: "深圳"}

别的和let一样。

4. 最后

如今能不用var就不用var,let和const已经可以完美替代。同时,相较于let应优先使用const,便于维护变量。