1. let和const命令
let命令
let是新增加的语法,该方法的用法和var是一样的,区别在于let声明的变量只能let所在的代码块内有效
{
let name='Tom';
var age=10;
}
console.log(name); //
console.log(age); //10
利用这一特性我们可以用来实现优化for循坏
var a=[];
for(let i=0;i<10;i++){
a[i]=function(){
return i;
};
}
console.log(a[6]()); //6
//在之前如果我们使用var定义变量,那么a[6]()的结果会是10,因为var定义的是全局变量,在循环中定义的i是全局变量,每一个a[i]返回的i的值都会指向全局的i,而全局的i在循环结束后值为10,所以导致运行的时候最后的值都是循环后的最后一轮的值,而使用了let后,每一次循环的i都是新的变量,在javascript的引擎内部会记住上一轮循环的值,在初始化本轮的变量i时,就会在上一轮的基础上进行计算并对本次的计算记过进行赋值,使用let对i赋值相当于我们在es5中使用闭包存储每一次i变量值的操作
//es5
var a=[];
var loop=function (i) {
a[i]=function () {
return i
}
}
for(var i=0;i<10;i++){
loop(i)
}
console.log(a[6]()); //6
//另外在for循环中使用let时需要注意,在循环处设置的变量那部分是属于父作用域,而循环体内部则是属于单独子作用域
for(let i=0;i<3;i++){
let i='abc';
console.log(i);
}
//在循环结束后会打印三次'abc',这说明循环内部的变量i和for循环定义的变量i不在同一个作用域,有各自单独的作用域,而如果我们使用var进行定义则会报错
let定义的变量不会发生变量提升的情况,也就是我们之前说的变量的预解析,let定义的语法一定要在let之后对变量进行引用,否则会报错,我们可以比较一下使用var和let定义变量的区别
console.log(name); //undefined
var name='tom';
console.log(age); //index.html:15 Uncaught ReferenceError: age is not defined
let age=18;
let定义变量会有一个暂时性死区的现象,简称TDZ,在ES6中明确规定,如果在块级作用域中存在let或const命令,从一开始就形成了封闭的作用域,凡是在let声明变量之前使用该变量是不合法的,会报错
var num=2;
if(true){
num='hello';
let num;//index.html:15 Uncaught ReferenceError: num is not defined
}
//也就是说在块级作用域中,在let定义的变量之前使用该变量都是报错,即使该变量已经在全局作用域中定义
还有一些其它情况的死区的现象,如下
function bar(x=y,y=2){
return [x,y];
}
bar();//index.html:12 Uncaught ReferenceError: y is not defined
function bar(x=2,y=x){
return [x,y]
}
console.log(bar());//[2,2]
//在第一个函数中,因为在参数x=y时,y还没有声明,属于死区
//在第二个函数中,参数y=x时,x已经赋值为2
var x=x; //undefined
let x=x; //Uncaught ReferenceError: y is not defined
//使用var声明变量时,var x=x; 可以理解为var x; x=undefined;
//而使用let时,由于不存在变量提升,所以改写法相当于在没有声明x时去使用x,所以会报错
let不允许在相同作用域内,重复声明同一个变量
{
var i=1;
let i=2
} //Uncaught SyntaxError: Identifier 'i' has already been declared
{
let i=1;
let i=2;
} // Uncaught SyntaxError: Identifier 'i' has already been declared
function fn(arg){
let arg;
} //Uncaught SyntaxError: Identifier 'arg' has already been declared
function fn(arg){
{
let arg;
}
}//不报错
//这里我们可以看出,1.函数的参数和函数内部时属于一个作用域内 2.使用了let定义变量后,不允许再次重复声明,即使是var也不可以
ES6允许任意的块级作用域嵌套,外层的块级作用域无法获取内层的块级作用域内的变量,内层作用域可获取外层作用域定义的变量,内层可以定义与外层相同的变量
{
let name='xm';
let age=18;
{
let name='Tom'
console.log(age);
}
}
let命令可以形成块级作用域,只在声明变量的块级作用域内起作用,不可以对变量进行重复声明,不会进行变量名提升,存在暂死性区域,在let定义变量之前对该变量进行引用会报错,在块级作用域外不能获取let定义的变量
const命令
const声明一个只读的常量,一旦声明,常量的值就不能更改,同时也意味着一旦我们使用const声明常量,需要立刻赋值,不能保留到以后赋值
const x='Tom';
x='xm';//index.html:14 Uncaught TypeError: Assignment to constant variable.
const x;
console.log(x);//Uncaught SyntaxError: Missing initializer in const declaration
const同样不支持变量提升,只在声明所在的块级作用域内有效,也存在暂死性区域,只能在声明的位置后面使用,并且不可以重复声明
const本质
const实际上保证的并不是变量的值不得改动,而是变量指向的内存地址不能改动,那么我们都知道,对于简单类型的数据来说,值就保存在变量指向的内存地址中,因此地址等同于值,但对于复杂类型的数据来说,地址保存的只是一个指针,const只能保证这个指针是固定的,并不能保证指针指向的地址内的数据结构不变
const obj={};
obj.name='xm';
console.log(obj.name);//xm
obj={};//Uncaught TypeError: Assignment to constant variable.
//我们为const定义的对象添加数据并不会报错,而且可以获取到添加的数据,但是如果想要为obj重新赋值会立即报错
const a=[];
a.push('hello');
a.length=0;
a=['hi'];//Uncaught TypeError: Assignment to constant variable.
//对于const定义的复杂类型,我们可以对其属性和数据进行添加或修改,但不能改变其地址
const用于声明一个不可变的变量,如果是简单类型的数据,那么该值不可以被修改,如果是复杂数据类型的话,可以对其属性进行修改,对数据进行添加,但是不能对其进行重新赋值操作,coust声明的变量不能被提升,只能在声明的块级作用域中使用,同样具有暂死性区域,不可以进行重复定义
在之前,我们使用var定义一个全局变量,那么该变量会被挂载到window(顶层对象)上,这也造成来了只要是全局属性,那么一定是顶级对象的属性,为了规避这个问题同时保持兼容性,在ES6中规定var和function定义的全局变量依旧是顶层对象的属性,但是由let,coust,class声明的全局变量,不属于顶层对象的属性,也就是说从ES6开始,顶层属性将会逐渐与全局变量脱钩
var a="a";
let b="b";
console.log(window.a); //a
console.log(window.b); //undefined