let和const的理解以及let,const,var的区别
let基本用法
{ let a = 1 var b = 2 } console.log(a) //Uncaught ReferenceError: a is not defined 未捕获引用错误:a是未定义 console.log(b) //2
概念:在ES6中凡是{}包裹的代码都是块级作用域,凡是在块级作用域中用let const声明的变量都在有一个暂时性死区
代码块内有效 let 是在代码块内有效,var 是在全局范围内有效
不能重复声明
let 只能声明一次, var 可以声明多次
{ let a = 1; let a = 2; var b = 3; var b = 4; } console.log(a) //Uncaught SyntaxError: Identifier 'a' has already been declared 未捕获的语法错误:已声明标识符“a” console.log(b) //4
for 循环计数器很适合用 let
for (var i = 0; i < 10; i++) { setTimeout(function () { console.log(i); //10次10 }) } for (let j = 0; j < 10; j++) { setTimeout(function () { console.log(j); //0123456789 }) }
另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (var i = 0; i < 3; i++) { var i = 'abc'; console.log(i); //输出一次abc } for (let j = 0; j < 3; j++) { let j = 'abc'; console.log(j); //输出三次abc }
这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。
不存在变量提升
var 的情况
console.log(b); // 输出undefined var b = 2;
let 的情况
console.log(a); // 报错Uncaught ReferenceError: Cannot access 'a' before initialization未捕获的引用错误:无法在初始化之前访问“a” let a = 2;
变量 b 用 var 声明存在变量提升,所以当脚本开始运行的时候,b 已经存在了,但是还没有赋值,所以会输出 undefined。
变量 a 用 let 声明不存在变量提升,在声明变量 a 之前,a 不存在,所以会报错。
暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var a = 123; if (true) { //console.log(a) //Uncaught ReferenceError: Cannot access 'a' before initialization 未捕获的引用错误:无法在初始化之前访问“a” a = 'abc'; let a; //console.log(a) }
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。
typeof x; // Uncaught ReferenceError: Cannot access 'x' before initialization 未捕获的引用错误:无法在初始化之前访问“x” let x; console.log(typeof x)
上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError。
作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。
typeof y console.log(typeof y) //undefined
上面代码中,y是一个不存在的变量名,结果返回“undefined”。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。
这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
有些“死区”比较隐蔽,不太容易发现。
function bar(x = y, y = 2) { return [x, y]; } console.log(bar()) // 报错Uncaught ReferenceError: Cannot access 'y' before initialization 未捕获的引用错误:无法在初始化之前访问“y” function bar(x = 2, y = x) { return [x, y]; } console.log(bar())// [2, 2]
上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于“死区”。如果y的默认值是x,就不会报错,因为此时x已经声明了。
另外,下面的代码也会报错,与var的行为不同。
var x = x; console.log(x) // 不报错 let y = y; console.log(y) // ReferenceError: y is not defined y是未定义的
上面代码报错,也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。
ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
const基本用法
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const a = 123; a = 3; console.log(a) //Uncaught TypeError: Assignment to constant variable. 上面代码表明 未捕获类型错误:分配给常量变量。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const a; console.log(a) // SyntaxError: Missing initializer in const declaration const声明中缺少初始值设定项
上面代码表示,const声明中缺少初始值设定项 ,对于const来说,只声明不赋值,就会报错。
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) { const a = 5; } console.log(a) //Uncaught ReferenceError: a is not defined a是未定义的
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) { console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization 未捕获的引用错误:无法在初始化之前访问“a” const a = 5; }
上面代码在常量a声明之前就调用,结果报错。
const声明的常量,也与let一样不可重复声明。
const foo = {};
为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
将 foo 指向另一个对象,就会报错
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。
不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
下面是另一个例子。
const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错Uncaught TypeError: Assignment to constant variable. 未捕获类型错误:分配给常量变量。
上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。
var,const,let的区别
var
支持变量声明与解析
不支持块级作用域
允许重复声明
let
不支持变量声明与解析
支持块级作用域
不允许重复声明
用let声明的变量或者方法只会在代码块中有效
const
不支持变量声明与解析
支持块级作用域
不允许重复声明,声明变量,一旦确定不允许被修改
声明变量必须赋值,不能跟var一样声明后再定义