let 与 var 声明变量
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b /
let
声明的变量只在它所在的代码块有效。===>块级作用域
var 声明的变量能返回正确的值。
for
循环的计数器,就合适使用let
命令
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i); //这里的i只能在循环体里用 ,循环体外引用就会报错会报错
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i); // 10
};
}
a[6]();
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i); //6
};
}
a[6]();
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i); //这里会连续输入3次 abc
}
循环条件var
变量i
是var
命令声明的,在全局范围内都有效,所以全局只有一个变量i
。每一次循环,变量i
的值都会发生改变,而循环内被赋给数组a
的函数内部的console.log(i)
,里面的i
指向的就是全局的i
。也就是说,所有数组a
的成员里面的i
,指向的都是同一个i
,导致运行时输出的是最后一轮的i
的值,也就是 10。
循环条件let
变量i
是let
声明的,当前的i
只在本轮循环有效,所以每一次循环的i
其实都是一个新的变量,所以最后输出的是6
。你可能会问,如果每一轮循环的变量i
都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i
时,就在上一轮循环的基础上进行计算。
最后一个3 次abc
。这表明函数内部的变量i
与循环变量i
不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 let
重复声明同一个变量)。改成var的话,只会输出一次abc
不存在变量提升
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
变量 foo
用var
命令声明,会发生变量提升,即脚本开始运行时,变量foo
已经存在了,但是没有值,所以会输出undefined
。变量 bar
用let
命令声明,不会发生变量提升。这表示在声明它之前,变量bar
是不存在的,这时如果用到它,就会抛出一个错误。
暂时性死区
var temp=123
if(true){
temp='adc'
let temp
}
存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
if (true) {
// 开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; //结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
在let
命令声明变量tmp
之前,都属于变量tmp
的“死区”
typeof x; // ReferenceError
let x;
上面代码中,变量x
使用let
命令声明,所以在声明之前,都属于x
的“死区”,只要用到该变量就会报错。因此,typeof
运行时就会抛出一个ReferenceError
。
typeof y // "undefined"
如果一个变量根本没有被声明,使用typeof
反而不会报错,在没有let
之前,typeof
运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
function init(x = y, y = 2) {
return [x, y];
}
init(); // 报错
报错的原因 因为参数x
默认值等于另一个参数y
,而此时y
还没有声明,属于“死区
function bar(x = 5, y = x) {
return [x, y]; //5 5
}
bar();
这样不会报错了
// 不报错
var x = x;
// 报错
let x = x;
暂时性死区。使用let
声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x
的声明语句还没有执行完成前,就去取x
的值,导致报错”x 未定义“.
不允许重复申明
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
function func(arg) {
let arg;
}
func() // 报错
function func(arg) {
{
let arg;
}
}
func() // 不报错
let
不允许在相同作用域内,重复声明同一个变量、 不能在函数内部重新声明参数。
为什么会出现块级作用域?
第一种场景,内层变量可能会覆盖外层变量
第二种场景,用来计数的循环变量泄露为全局变量 ===> for 循环 的var i的变量
const
const a=2
a=3 //报错
const
声明一个只读的常量。一旦声明,常量的值就不能改变
const 一旦声明,就必须赋值,不然就会报错
const
声明的常量,也与let
一样不可重复声明。
const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
常量a
是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a
,就会报错。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {};
常量foo
储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo
指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。