1.let命令
ES5和以前版本的js采用var声明变量,ES6 新增了 一个JavaScript 关键字: let 。let 的用法类似于var,但相较于var具有以下特点
1.1块作用域
let所声明的变量只在let命令所在的代码块有效
{
let a=10;
var b=1;
}
console.log(b);
console.log(a);
输出结果b是1,a是not defined(此处注意not defined和undefined区别,undefined说明变量声明但没有定义,not defined说明该变量甚至没有声明)
1.2不能重复声明
function fn(){
let a=10;
let a=1;
}// Identifier 'a' has already been declared
function fn(){
let a=10;
var a=1;
}//Identifier 'a' has already been declared
function fn(){
var a=10;
let a=1;
}//Identifier 'a' has already been declared
function fn(){
var a=10;
var a=1;
}
四段代码中只有第四段不会报错,因为使用var命令时编辑器会判断如果已经有声明的同名变量时忽略var关键字,然后直接赋值。
而let不允许在相同的作用域内重复声明同一个变量,不管该变量的两次声明都用let或用let和var都会报错。因此,也不能在函数内部重新声明参数。
function func(arg) {
let arg;
}//报错
function func(arg) {
{
let arg;
}
}//不报错
1.3没有变量提升
console.log(a); // undefined
var a = 1;
var声明的变量无论其实际声明位置在何处,都会被视为声明于所在函数(或全局)的顶部,这就是变量提升。因此实际上的代码可看做为:
var a;
console.log(a); // undefined
a = 1;
而let不具有变量提升的性质,所以输出为变量b没有被声明
console.log(b); //b is not defined
let b = 1;
1.4暂时性死区
ES6明确规定,如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。
在代码块内,使用let命令声明变量之前,该变量都是不可用的,这个区域被称为“暂时性死区”(TDZ)。
var temp=123;
if(true){
temp='abc';// Cannot access 'temp' before initialization
let temp;
}
此段代码中虽然定义了全局变量temp,但块作用域中又let了一个局部变量temp,导致该块级作用域成为封闭作用域,不被全局变量temp影响,所以对局部变量temp进行赋值之前还没有声明该变量,即产生报错,这个区域就是暂时性死区
1.5不影响作用域链
每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链。
当JavaScript查找变量x的时候,会从当前作用域开始跟随作用域链向上查找,直到找到x变量的声明,若到达全局作用域中仍未找到,则抛出一个引用错误(ReferenceError)异常
let a=123
function fun(){
console.log(a)//123
}
此处fun函数要输出变量a但函数内没有,就会跟随作用域链向上查找,let不影响作用域链的效果,所以找到a的赋值为123
2.块级作用域
ES5只有全局作用域和函数作用域,从而会导致一些问题出现:
var tmp=new Date();
function f(){
console.log(tmp);
if(false){
var tmp='hello';
}
}
f();//undefined
此处原本想让if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量,但由于变量提升,导致内层的tmp变量覆盖了外层的tmp变量,输出undefined
var s='hello';
for(var i=0;i<s.length;i++){
console.log(s[i]);
}
console.log(i);//5
此处变量i只用来控制循环,但是循环结束后,它并没有消失,而是泄露成了全局变量。
因此,let为js新增了块级作用域,块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域,块作用域具有以下特点:
2.1允许作用域任意嵌套
块作用域多层嵌套,不影响内层作用域读取外层作用域的变量
{
let a = 2;
{
a++;
{
console.log(a)//3
}
}
}
但外层作用域无法读取内层作用域的变量
{
console.log(a)//a is not defined
{
let a= 2
}
}
2.2内层作用域可以定义外层作用域的同名变量
上文有说外层作用域无法读取内层作用域的变量,所以即使内层作用域定义的变量与外层同名,也不会报错
{
let a=123
{
let a=456
}
}
2.3不需要立即执行匿名函数
没有块作用域之前,为了防止前文举例的内层覆盖外层变量,计数循环变量泄露为全局变量等问题,广泛采用立即执行匿名函数(IIFE),块作用域的出现使IIFE不再必要
(function() {
var a = 0;
}());//立即执行匿名函数
3.const命令
const用来声明一个只读的常量,一旦声明,其值不能改变且必须立即初始化
const a = 2;
a //2
a = 3;// Assignment to constant variable.
const b;//Missing initializer in const declaration
除此之外,与let用法一致,即只在声明所在的块级作用域内有效、声明的常量也是不提升、同样存在暂时性死区、不可重复声明
而在实际上,const保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量,但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针
const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了
const foo={y:10};
foo.x=100;
console.log(foo);//{"y": 10,"x": 100}
foo={n:1000};// Assignment to constant variable.
foo指向对象的属性可以增加,但不能把新的对象赋给foo
4.顶层对象
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
这样的设计带来了很多问题,ES6 为了改变这一点,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性