ECMAScript和JavaScript关系:
ECMAScript是JavaScript的语言规范,JavaScript是ECMAScript的实现和扩展。ES6是ECMAScript的简称。
这里先介绍一下块作用域的概念:
JS中作用域有:全局作用域、函数作用域,没有块作用域的概念。块作用域是ES6新增的,是指由{}包括起来的内容,if语句和for语句里面的{}也属于块作用域。
var、let、const的区别
1、var定义的变量,没有块的概念,可以跨块访问,不能跨函数访问;
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问;
const用来定义常量,使用时必须初始化,只能在块作用域里访问,而且不能修改。
<script type="text/javascript">
//块作用域
{
var a = 1;
let b = 2;
const c = 3;
// c = 4; //报错,因为const定义的常量已经初始化了,且不能修改
var aa;
let bb;
// const cc; //报错,因为const定义的时候必须要初始化
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(aa); // undefined,因为没有初始化
console.log(bb); // undefined,因为没有初始化
}
console.log(a); // 1,因为var声明的变量是可以跨块访问的
console.log(b); // 报错,因为let定义的变量只能在块作用域里访问
console.log(c); // 报错,因为let定义的变量只能在块作用域里访问
//函数作用域
(function A(){
var d = 5;
let e = 6;
const f = 7;
console.log(d); // 5
console.log(e); // 6(在同一个{}中,也属于同一个块,可以正常访问到)
console.log(f); // 7(在同一个{}中,也属于同一个块,可以正常访问到)
})();
// console.log(d); //报错,var定义的变量不可以跨函数访问
// console.log(e); //报错,let定义的变量不可以跨函数访问
// console.log(f); //报错,const定义的常量不可以跨函数访问
2、var存在变量提升,let和const不存在变量提升
“变量提升”即变量可以在声明之前使用,值为undefined.
//var的情况
console.log(foo); // 输出undefined
var foo = 2;
// let的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
// const的情况
console.log(MAX); // 报错ReferenceError
const MAX = 5;
let和const命令声明,不会发生变量提升,这表明在声明之前,bar和MAX时不存在的,这时如果用到它,就会抛出一个错误。
3、暂时性死区
只要块级作用域内存在let、const命令,它所声明的变量/常量就绑定(binding)这个区域,不在受外部的影响。
var tmp = 123;
if(true) {
tmp = 'abc'; //报错ReferenceError
let tmp;
}
if(true) {
console.log(MAX); // 报错ReferenceError
const MAX = 5;
}
虽然已经存在全局变量tmp,但是在if这个块作用域内let又声明了一个局部变量tmp,所以块里面的tmp = ‘abc’;中的tmp是指let声明的tmp,因为在let声明之前就调用了,所以会报错。const同理。
ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错,这在语法上称为“暂时性死区”(temporal dead zone,简称TDZ)。
有些“死区”比较隐蔽,不太容易发现。
function bar(x = y, y = 2){
return [x,y];
}
bar(); //报错,因为将y赋值给x,但是此时y还没有声明,属于“死区”
function bar(x = 2, y = x){
return [x,y];
}
bar(); // [2,2]
注:下面的代码也会报错,与var的行为不同。
var x = x; // 不报错
let x = x; // 报错ReferenceError:x is not defined
4、不允许重复声明
let和const不允许在相同作用域内重复声明。
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
function func() {
var message = "Hello!";
let age = 25;
//以下两行都会报错
const message = "Goodbye!";
const age = 30;
}
因此不能在函数内部重新声明参数。
function func(arg) {
let arg;
}
func(); // 报错
function func(arg) {
{
let arg;
}
}
func(); // 不报错
不能用let和const完全取代var
1、从上面的解析中,我们可以看出他们的作用域和功能各有不同;
2、老版本的浏览器不支持let。不仅如此,而且有些最新的浏览器也还没有支持let。
经典面试题
var callbacks = [];
(function() {
for(var i = 0; i < 5; i++) {
callbacks.push(function() { return i; } )
}
})();
console.log(callbacks.map(function(cb) { return cb(); } ))
这里存在一个“hoisting陷阱”,即声明提前陷阱。这里的i使用var声明的,所以变量声明会被提到函数顶部,变成
var callbacks = [];
(function() {
var i;
for(i = 0; i < 5; i++) {
callbacks.push(function() { return i; } )
}
})();
console.log(callbacks.map(function(cb) { return cb(); } ))
经过整个for循环,callbacks里面存储的5个函数指向的同一个变量i的值已经是5.所以最终打印出来的是[5, 5, 5, 5, 5], 那么如何使返回的结果是[0, 1, 2, 3, 4, 5]呢?
var callbacks = [];
(function() {
for(let i = 0; i < 5; i++) {
callbacks.push(function() { return i; } )
}
})();
console.log(callbacks.map(function(cb) { return cb(); } ))
使用let声明变量i就可以了,因为let拥有块作用域,不会被提升到函数顶部,所以i每次循环都有独立的值。