let 和 const 命令
1、let的基本用法与块级作用域
用法与var类似,但是let所声明的变量,只在命令所在的代码块内有效。
{
var a = 1;
let b = 2;
}
console.log(a) // 1
console.log(b) // b is not defined
如上面代码所示,在花括号外打印let声明的变量,会报错,而var可正常显示,说明let只在命令所在的代码块内有效
举个栗子
for (let i = 0; i < 5; i++) {}
console.log(i) //i is not defined
上面代码中,计数器i只在for循环体内有效,在循环体外引用就会报错。
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0)
i //55555
}
上面代码中,变量i是var声明的,在全局范围内都有效,所以全局只有一个变量i。而setTimeout是一个异步行为,而js执行机制是单线程的,代码的执行是从上到下,依次执行,碰到异步的代码会将其插入到任务队列当中等待,直到同步代码都执行完毕之后,任务队列中的异步代码才依次加载,而这时候全局变量i的值已经变成5了,所以打印出来的i全都为5
for (let j = 0; j < 5; j++) {
setTimeout(() => console.log(j), 0)
j // 0 1 2 3 4
}
//你也可以当成是声明了五个let块级作用域
{let i = 0}
{let i = 1}
{let i = 2}
{let i = 3}
{let i = 4}
变量 j 是用 let 声明的,当前的 j 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 01234。
function f1() {
let n = 5;
if (true) {
let n = 10;
let m = 20;
console.log(n); // 10
}
console.log(m); // m is not defined
console.log(n); // 5
}
上面代码中,表明在不同的代码块内的变量不会相互影响,同时外层的代码块也无法访问内层的代码块的变量,不同代码块可以用同名的变量。
2、不存在变量提升
// var 的情况
console.log(a); // 输出undefined
var a= 2;
// let 的情况
console.log(b); // 报错ReferenceError,初始化之前无法访问'b'
let b= 2;
上面代码中,变量a用var命令声明,会发生变量提升,即脚本开始运行时,变量a已经存在了,但是没有值,所以会输出undefined。变量b用let命令声明,不会发生变量提升。这表示在声明它之前,变量b是不存在的,这时如果用到它,就会抛出一个错误。
3、位于函数或代码顶部的var声明会给全局对象新增属性, 而let不会
var a = 123;
let b = 321;
console.log(this.a); // 123
console.log(this.b); // undefined
原因:全局环境记录在逻辑上是单个记录,但它被指定为封装对象环境记录和声明性环境记录的组合。对象环境记录的关联领域的全局对象的基础对象。此全局对象是全局环境记录的GetThisBinding具体方法返回的值。全局环境记录的对象环境记录组件包含所有内置全局变量的绑定(第18节)以及全局代码中包含的FunctionDeclaration,GeneratorDeclaration或VariableStatement引入的所有绑定。全局代码中所有其他ECMAScript声明的绑定包含在全局环境记录的声明性环境记录组件中。(网上看到的,但是没太懂
4、不允许重复声明
// 报错,变量已被声明
function () {
let a = 10;
var a = 1;
}
// 报错
function () {
let a = 10;
let a = 1;
}
function func(arg) {
let arg; // 报错,参数重复声明同
}
function func(arg) {
{
let arg; // 不报错
}
}
let不允许在相同作用域内,重复声明同一个变量。因此,不能在函数内部重新声明参数。
5、暂存死区
与通过 var 声明的有初始化值 undefined 的变量不同,通过 let 声明的变量直到它们的定义被执行时才初始化。在变量初始化前访问该变量会导致 ReferenceError。该变量处在一个自块顶部到初始化处理的“暂存死区”中
console.log(a); // undefined
console.log(b); // 报错,初始化之前无法访问
var a= 1;
let b= 2;
6、暂存死区与typeof
typeof c // "undefined"
typeof b; //undefined
typeof a; // 报错,初始化之前无法访问
let a;
var b;
如上面的代码所述,一个用var声明的变量和一个未声明的变量,通过typeof返回的结果都是一个undefined,而使用typeof检测在暂存死区中的变量, 会抛出ReferenceError异常。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
注意
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。如果y的默认值是x,就不会报错,因为此时x已经声明了。
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
另外,下面的代码也会报错,与var的行为不同。
在变量x的声明语句还没有执行完成前,就去取x的值,导致报错X未定义
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
const命令
1、基本用法
// 定义常量MY_FAV并赋值7
const MY_FAV = 7;
// 报错 - Uncaught TypeError: Assignment to constant variable.
MY_FAV = 20;
// MY_FAV is 7
console.log('my favorite number is: ' + MY_FAV);
// 尝试重新声明会报错
// Uncaught SyntaxError: Identifier 'MY_FAV' has already been declared
const MY_FAV = 20;
// MY_FAV 保留给上面的常量,这个操作会失败,字符'MY_FAV'已被声明
var MY_FAV = 20;
// 也会报错
let MY_FAV = 20;
const声明一个只读的常量,但常量的值是无法(通过重新赋值)改变的,也不能被重新声明;常量在声明的时候可以使用大小写,但通常情况下全部用大写字母。
const MY_FAV;// SyntaxError: Missing initializer in const declaration
const一旦声明变量,就必须立即初始化,不能留到以后赋值,否则会报错
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
2、常量可以定义成对象和数组
本质:
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。
总结
let命令总结
- 声明的变量仅仅在块级作用域有效
块级作用域:外层作用于无法读取内层作用于周中的变量,内层作用域可以定义外层作用于中的同名变量 - 不存在变量提升
- 暂时性死区
只要在块级作用域内存在let命令,他所生声明的变量就“绑定”这个区域,不在受外部影响。 - 位于函数或代码顶部的var声明会给全局对象新增属性, 而let不会
- 不允许重复声明
const命令总结
声明基本类型:
- 一旦声明,值不可改变;
- 声明的时候必须赋值,否则报错;
- 只在块级作用域内有效
- 存在暂时性死区
- 不可重复声明
声明对象:
- 变量名指向数据所在的地址,但是只保证变量名指向的地址不变。
- 不可以重新赋值{不能让变量名指向另一个地址};
- 对象本身是可变的,依然可以添加新的属性。
好了,let和const命令就讲到这里了,喜欢的小伙伴可以动动手指点点赞,让小作者有更大的动力去分享更多的知识给大家,谢谢!