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,便于维护变量。