let
let
用于定义一个变量,这和ES5
中的var
是一样的作用。但是var
定义的变量会存在变量污染的问题。
var a = 1;
var a = 2;
console.log(a); // 2
虽然可以通过立即执行函数来解决全局变量污染的问题,但是在立即执行函数内的问题无法得倒解决。
(function(){
var a = 1;
var a = 2;
console.log(a); // 2
})()
1、同一个变量不容许重复声明
这个特点也就直观的解决了var
变量污染的问题
let a = 1;
let a = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared
var b = 1;
let b = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared
// 在函数内部声明也是一样的
function test() {
let a = 1;
let a = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared
}
// let 声明不能和形参同名
function test(a) {
let a = 2;
console.log(a);
}
test(1)
// 变量也不能和函数同名
{
let a = 1;
function a() {}
console.log(a); // Identifier 'a' has already been declared
}
2、块级作用域
在ES5
中var
是不受块作用域{}
限制的
if(true){
var a = 1;
}
console.log(a); // 1
ES6
的let
声明的变量在块内{}
是独立的
if(true){
let a = 1;
}
console.log(a); // a is not defined
function test(a) {
{
let a = 2;
console.log(a); // 2
}
console.log(a); // 1
}
test(1);
3、暂时性死区
let
不会进行预编译的变量提升,在let
之前访问变量会存在暂时性死区,使用var
定义的变量会进行变量提升
var a = a;
console.log(a); // undefind
let b = b;
console.log(b); // Cannot access 'b' before initialization
// 预编译阶段:
var a;
a = a;
// let 不会进行变量提升,所以报错:初始化前无法访问“b”
// 全局作用域内,无法在 let 之前访问变量
console.log(a); // Cannot access 'a' before initialization
let a = 10;
// 函数的作用域内也无法在 let 之前访问变量
function test() {
console.log(a); // Cannot access 'a' before initialization
let a = 10;
}
test();
console.log(typeof a); // Cannot access 'a' before initialization
let a = 1;
4、let
只能在当前的块级作用域内生效
这个特点可以和特点2合并成一个
{
// let 声明的变量只作用于当前 {} 内
let a = 2;
}
console.log(a); // a is not defined
function test(){
let a = 2;
}
console.log(a); // a is not defined
if(true){
var a = 2;
let b = 3;
}
console.log(a); // 2
console.log(b); // b is not defined
// for 循环中 () 然后属于 {} 块作用域的范畴
for (let i = 0; i < 3; i++) {}
console.log(i); // i is not defined
// 针对 for 循环还有一个需要注意的地方
for (let i = 0; i < 10; i++) {
let i = "a";
console.log(i); // 10 个 a
}
for (let i = 0; i < 10; i++) {
var i = "a";
console.log(i); // 报错
}
// 解析
// 我们可以把()和 {} 理解为两个块作用域
// 第一个循环相当于一个父子块作用域
{
let i;
{
let i;
}
}
// 第二个循环的时候,因为 var 会变量提升,所以会出现这样
var i;
{
let i;
{
}
}
🛳 下面是一个经典的**for**
循环执行方法的题目:
var arr = [];
for (var i = 0; i < 5; i++) {
arr.push(function () {
console.log(i);
});
}
for (var i = 0; i < 5; i++) {
arr[i]();
}
// 解析
// 答案: 0 1 2 3 4
// 这道题猛然一看就认为是 5 个 5
// 因为 var 会变量提升,for 循环可以拆分出来
var i = 0;
for (;i < 5;) {
// ../
i++
}
// 实际上第二次循环的时候,又 var i 相当于把全局的 i 覆盖了
var i = 5;
for (var i = 0; i < 5; i++) {
// i = 0
// i = 1
}
// 所以覆盖的时候去执行方法,方法去寻找全局的 i,此时就打印出来 0 1 2 3 4
// 正常的题目是这样滴:
var arr = [];
for (var i = 0; i < 5; i++) {
arr.push(function () {
console.log(i);
});
}
for (var j = 0; j < 5; j++) {
arr[j](); // 这个时候打印的肯定是 5 个 5
}
// ==========
// 当我们把 var 更改为 let 的时候就可以正常打印出 0 1 2 3 4 了
var arr = [];
for (let i = 0; i < 5; i++) {
arr.push(function () {
console.log(i);
});
}
for (var j = 0; j < 5; j++) {
arr[j]();
}
// ==========
// 解析
// 因为 let 有块作用域的功能,所以第一次循环的时候相当于生成 5 个独立的块
{
let i = 0;
function () {
console.log(i);
}
}
// 第二次循环的时候,函数会往上找块作用于内的 i ,所以也就能正常的打印了
const
const
和let
基本上类似,只不过const
主要用来定义常量(不可更改的数据)。const
的特点:
1、定义常量必须赋值
const a;
console.log(a) // Missing initializer in const declaration
2、定义常量后不可更改
const a = 10;
a = 11; // TypeError: Assignment to constant variable.
3、不容许重复声明
const a = 10;
const a = 12; // Identifier 'a' has already been declared
var a = 10;
const a = 12; // Identifier 'a' has already been declared
4、暂时性死区
和let
定义变量是一样的
console.log(a); // Cannot access 'a' before initialization
const a = 10;
5、块作用域
{
const a = 10;
}
console.log(a); // a is not defined
6、定义引用对象无效
const
对于「原始数据类型」后更改值是不被容许的,但是定义「引用数据类型」后更改它的属性仍然是可以的,也就是说**const**
只能保证引用值的指针不被改变,并不能保证引用值的属性不被改变
const person1 = {};
person1.name = "张三";
console.log(person1); // {name: "张三"}
const person2 = {};
person2 = {}; // Assignment to constant variable.
// 我们可以利用 Object.freeze() 方法来冻结对象,达到不能操作属性的目的
const obj = {};
Object.freeze(obj);
obj.name = "张三"
console.log(obj); // {}
顶层对象
在使用var
定义数据的时候,相当于在window
对象上新增了一个属性,这样某些情况下就会造成混乱。
var a = 1;
console.log(window.a); // 1
而let
和const
为了解决这个问题,定义的数据不会存在顶层对象window
上。
let a = 1;
const b = 2;
console.log(window.a); // undefined
console.log(window.b); // undefined