前言:随着2015年6月ECMAScript6.0(简称ES6)发布,有一些新的命令进入前端开发领域,即let、
const、import和class命令。其中的let和const是用于声明变量的,这两个命令跟ES5的var有许多不同,并且
let和const也有一些细微的差别。在阅读了阮一峰老师的文档后,
整理了这篇文章,分享给大家。
目录
简单介绍:
let的用法类似于var,但是let只在所在的代码块内有效,所以一般用let替代var。而const通常用于声明常量。
首先看下面这张表:
声明方式 | 变量提升 | 暂时性死区 | 重复声明 | 初始值 |
---|---|---|---|---|
var | 允许 | 不存在 | 允许 | 不需要 |
let | 不允许 | 存在 | 不允许 | 不需要 |
const | 不允许 | 存在 | 不允许 | 需要 |
一、var方式定义变量有什么bug
1. js没有块级作用域
在js函数中的var声明,其作用域是函数体的全部。
for(var i=0; i<10; i++){
var aa = 'a';
}
console.log(aa) // a
console.log(i) //10
已经跳出for循环了,但是还可以访问到循环内定义的变量a,甚至连变量i都可以访问到
2. 循环内变量过度共享
for(var i=0; i<3; i++){
setTimeout(function(){
console.log(i)
},1000);
}
控制台输出了3个3,而不是预想的 0、1、2。
事实是,循环本身及三次timeout回调均共享唯一变量i。当循环结束执行时,i的值为3,所以当第一个
timeout执行时,调用的i也就是3了。
二、区别
var和let/const的区别
1.块级作用域
2.不存在变量提升
3.暂时性死区
4.不可重复声明
5.let、const声明的全局变量不会挂在顶层对象下面
const命令的两个注意点:
1.const声明之后必须马上赋值,否则会报错
2.const简单类型一旦声明就不能更改,复杂类型(数组、对象)指针指向的地址不能更改,内部数据
可以更改。
为什么需要块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域
这会导致许多不合理的场景:
1.内层变量可能覆盖外层变量
2.用来计数的循环变量泄露为全局变量
var tmp = [1,2,3];
function f(){
console.log(tmp);
if(false){
var tmp = 'this is a string';
}
}
f(); // undefined
var str = 'string';
for (var i=0;i<str.length;i++){
console.log(str[i]);
}
console.log(i);
// s
// t
// r
// i
// n
// g
// 6
块级作用域
- 作用域
function foo(){
let n = 7;
if(true){
let n = 9;
console.log(n); //9
}
console.log(n); //7
}
foo();
- 块级作用域可以任意嵌套
{{
{let say = 'hello world';}
console.log(say); // 报错Uncaught ReferenceError: say is not defined
}}
块级作用域声明函数:
在块级作用域声明函数,因为浏览器要兼容老代码,会产生一些问题:
在块级作用域声明函数,最好使用匿名函数的形式
if(true){
let a = function(){};
}
不存在变量提升
变量提升:在同一作用域下,变量可以在声明之前使用,值为undefined
ES5使用var声明变量时,经常会出现变量提升的现象
// var 情况
console.log(aaa); // 输出 undefined
var aaa = '1111';
// let 情况
console.log(aaa); //Uncaught ReferenceError: aaa is not defined
let aaa = '111';
暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明
之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
if(true){
// TDZ开始
aaa = 'abc'; //Uncaught ReferenceError
console.log(aaa); //Uncaught ReferenceError
let aaa; // TDZ结束
console.log(aaa); //undefined
aaa = '111';
console.log(aaa); //111
}
暂时性死区和不能变量提升的意义在于:
为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。
“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。
typeof x; // ReferenceError
let x;
但是,如果一个变量根本没有被声明,使用typeof反而不会报错。
typeof undeclared_variable // undefined
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
不允许重复声明
在测试的时候可能会出现这样的情况:var aa=‘声明一个变量’;const aa=‘重复声明一个变量不报错’;
出现这种情况是因为babel在转化的时候做了一些处理,在浏览器控制台中测试,就会报错。
let、const不允许在相同作用域内,重复声明同一个变量
function foo(){
let aaa = 10;
var aaa = '111111111'; //Uncaught SyntaxError: Identifier 'aaa' has already been declared
}
function foo(){
var aaa = '111111111';
let aaa = 10;
}
function foo(arg){
let arg = 111; //Uncaught SyntaxError: Identifier 'arg' has already been declared
}
let、const声明的全局变量不会挂在顶层对象下面
- 浏览器环境的顶层对象是:window
- node环境的顶层对象是:global
- var声明的全局变量会挂在顶层对象下面,而let、const不会挂在顶层对象下面。
var aa = 111;
// 如果在node环境,可以写成global.aa
// 或者采用通用方法,写成this.aa
console.log(window.aa)
let bb = 222;
console.log(window.bb); // undefined
三、const 命令
- 声明之后,必须马上赋值
let aa; var aa; // 不报错
const aa2 = '赋值';
const aa2; // Uncaught SyntaxError: Missing initializer in const declaration
2.const 声明值之后就不能改变
简单类型:不能改动
const aa = '不能改变';
aa = '报错'; // Uncaught TypeError: Assignment to constant variable.
复杂类型:变量指针不能变
const aa = ['do not update'];
aa[0] = 'update array'; // 不报错
aa = ['重新给数组赋值']; // Uncaught TypeError: Assignment to constant variable.
const bb = {
a1:'复杂类型'
};
bb.a1 = 'object'; // 不报错
bb = {
a1:'new 复杂类型'
}; // Uncaught TypeError: Assignment to constant variable.
如果真想讲对象冻结,应该使用Object.freeze方法
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。
const所说的一旦声明值就不能改变,实际上指的是:变量指向的那个内存地址所保存的数据不得改动
。简单类型(number、string、boolean):内存地址就是值,即常量(修改就报错)
。复杂类型(对象、数组等):内存地址保存的是一个指针,const只能保证指针是固定的(总是指向同一个
地址),它内部的值是可以改变的。
所以,只要不重新赋值整个数组/对象,const定义的变量时可以修改的,因为保存的是一个指针,所以对数组
用push、shift、splice等方法也是允许的。
复杂类型还有函数等。