var、let、const 的区别
var 关键词#
- var 声明作用域
var 定义的变量,没有块的概念,可以跨块访问,不能跨函数访问
function test() {
var message = "hello world"; // 局部变量
}
test();
console.log(message); // 报错
函数 test () 调用时会创建变量 message 并给它赋值,调用之后变量随即被销毁。因此,在函数 test () 之外调用变量 message 会报错
在函数内定义变量时省略 var 操作符,可以创建一个全局变量
function test() {
message = "hello world"; // 局部变量
}
test();
console.log(message); // hello world
省略掉 var 操作符之后,message 就变成了全局变量。只要调用一次函数 test (),就会定义这个变量,并且可以在函数外部访问到。在局部作用域中定义的全局变量很难维护,不推荐这么做。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出 ReferenceError。
- var 声明提升
var 在 js 中是支持预解析的,如下代码不会报错。这是因为使用 var 声明的变量会自动提升到函数作用域顶部:
function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined
javaScript 引擎,在代码预编译时,javaScript 引擎会自动将所有代码里面的 var 关键字声明的语句都会提升到当前作用域的顶端,如下代码:
function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined
let 声明#
- let 声明作用域
let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,而 var 可以跨块访问
// var定义的变量
if (true) {
var name = 'Matt';
console.log(name); // Matt
}
console.log(name); // Matt
// let定义的变量
if (true) {
let age = 26;
console.log(age); // 26
}
console.log(age); // ReferenceError: age没有定义
let 也不允许同一个块作用域中出现冗余声明(重复声明)
var name;
var name;
let age;
let age; // SyntaxError;标识符age已经声明过了
- 暂时性死区
let、const 与 var 的另一个重要的区别,let、const 声明的变量不会在作用域中被提升。ES6 新增的 let、const 关键字声明的变量会产生块级作用域,如果变量在当前作用域中被创建出来,由于此时还未完成语法绑定,所以是不能被访问的,如果访问就会抛出错误 ReferenceError。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区。
// name会被提升
console.log(name); // undefined
var name = 'Matt';
// age不会被提升
console.log(age); // ReferenceError:age没有定义
let age = 26;
- 全局声明
与 var 关键字不同,var 定义的全局变量会挂载到 window 对象上,使用 window 可以访问,而 let 在全局作用域中声明的变量不会成为 window 对象的属性
var name = 'Matt';
console.log(window.name); // 'Matt'
let age = 26;
console.log(window.age); // undefined
- for 循环中的 var、let 声明
for 循环中 var 定义的迭代变量会渗透到循环体外部:
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 和 let 定义 for 循环中的变量,循环里使用定时器 setTimeout 后循环结果如下代码:
for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 输出5、5、5、5、5
for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 输出0、1、2、3、4
let 是在代码块内有效,var 是在全局范围内有效。let 只能声明一次 ,var 可以声明多次。
当同步代码执行完毕后,开始执行异步的 setTimeout 代码,执行 setTimeout 时需要从当前作用域内寻找一个变量 i,for 循环执行完毕,当前 i=5,执行 setTimeout 时输出为 5,任务队列中的剩余 4 个 setTimeout 也依次执行,输出为 5。
变量 j 是用 let 声明的,当前的 i 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 0-4。
const 声明#
const 的行为与 let 基本相同,唯一一个重要的区别是:
const 是用来定义常量的,而且定义的时候必须赋值,不赋值会报错,定义之后是不允许被修改的,修改 const 声明的变量会导致运行时错误。
const age = 26;
age = 36; // TypeError: 给常量赋值
// const也不允许重复声明
const name = 'Matt';
const name = 'Nicholas'; // SyntaxError
// const声明的作用域也是块
const name = 'Matt';
if (true) {
const name = 'Nicholas';
}
console.log(name); // Matt
而 const 声明的变量是一个对象时,修改这个对象内部的属性并不会报错。
这是因为 const 声明的是栈区里的内容不能修改,基本数据类型的值直接在栈内存中存储,而引用数据类型在栈区保存的是对象在堆区的地址,修改对象的属性,不会修改对象在栈区的地址,如果重新给对象 person 赋值,则会报错。
const person = {
name: 'Lili'
};
person.name = 'Matt'; // ok
JavaScript 引擎会为 for 循环中的 let 声明分别创建独立的变量实例,虽然 const 变量跟 let 变量很相似,但是不能用 const 来声明迭代变量(因为迭代变量会自增):
for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值
不过,如果你只想用 const 声明一个不会被修改的 for 循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对 for-of 和 for-in 循环特别有意义:
let i = 0;
for (const j = 7; i < 5; ++i) {
console.log(j);
}
// 7, 7, 7, 7, 7
for (const key in {a: 1, b: 2}) {
console.log(key);
}
// a, b
for (const value of [1,2,3,4,5]) {
console.log(value);
}
// 1, 2, 3, 4, 5
本文由 note.batype.com 导入。
相关文章
- Optional chaining 语法编程
Optional chaining 是一个现代 JavaScript 语法特性,允许开发者在读取对象内部嵌套属性时安全地处理 undefined 或 null 值。这种语法通过 ?. 操作符实现,可以
- 对原型、原型链的理解
在 JavaScript 中是使用构造两数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造西数的所有实例共享的属性和方法。 当
- async/await 的理解
async/await 其实是 Generator 的语法糖,它能实现的效果都能用 then 链来实现,它是为优化 then 链而开发出来的。从字面上来看,async 是 “异步” 的简写,await