1. let命令
用法let
用来声明变量;但是let
所声明的变量只有在let
命令所在的代码块内有效。1
2
3
4
5
6{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined
b // 1
1 | var a = []; |
上述代码中,变量i是var声明的,在全局范围内都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出的是最后一轮的i的值。1
2
3
4
5
6
7var a = [];
for(let i=0; i<10; i++){
a[i] = function(){
console.log(i);
};
}
a[6]();
上述代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6;
不存在变量提升1
2
3
4
5// 脚本运行时,变量foo已经存在了,但是没有赋值,即undefined
console.log(foo); // undefined
console.log(bar); // ReferenceError
var foo = 2; // 变量提升;
let bar = 2; // 变量不会提升,所以在声明之前调用会报错;
暂时性死区1
2
3
4
5var tmp = 123;
if(true){
tmp = 'abc';
let tmp;
}
上述代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定了这个块级作用域,所以在let声明变量前,对tmp赋值会报错;1
2
3// 暂时性死区意味着typeof不再是一个百分百安全的操作了;
typeof x; // ReferenceError
let x;
ES6规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
暂时性死区就是:只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量之后才可以使用该变量。
不允许重复声明1
2
3
4
5
6
7function(){
let a = 10;
var a = 1; // 报错
}
function fun(arg){
let arg; // 报错
}
2.块级作用域
ES5中没有块级作用域的bug
内层变量覆盖外层变量1
2
3
4
5
6
7
8var tmp = new Date();
function f(){
console.log(tmp); // 提升后的内部变量覆盖了外部变量,且值为undefined
if(false) {
var tmp = 'hello'; // 变量提升;
}
}
f(); // undefined;
用来计数的循环变量泄漏为全局变量1
2
3
4
5
6var s = 'hello';
for(var i=0; i<s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5;
// 变量i只用来控制循环,但是循环结束后,它并没有消失,泄漏成了全局变量。
ES6的块级作用域1
2
3
4
5
6
7
8
9// let实际上为JavaScript新增了块级作用域
function f(){
let n = 5;
if(true) {
let n = 10;
}
console.log(n); // 5;
// 外层代码块不受内层代码块的影响;
}
ES6允许块级作用域的任意嵌套,且外层作用域无法读取内层作用域的变量1
{{{ let insane = 'hell' }}}
块级作用域的出现,实际上是替代了立即执行匿名函数(IIFE)1
2
3
4
5
6
7
8// IIFE
(function(){
var tmp = ...;
}());
// ES6
{
let tmp = ...;
}
ES6引入的块级作用域,明确允许在块级作用域之中声明函数;并且块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。1
2
3
4
5
6
7
8
9// ES5
function f() {console.log('outside'); }
(function() {
if(false){
// 这里的函数声明会被提升到if语句外部
function f(){ console.log('inside'); }
}
f(); // inside;
}());
1 | // ES6 |
应该避免在块级作用域内声明函数;如需要,应该写成函数表达式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}
// 函数表达式
{
let a = 'secret';
let f = function(){
return a;
}
}
3.const命令
const
声明一个只读的常量;一旦声明,常量的值就不能改变。const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。1
2
3
4if(true) {
const max = 5;
}
max // max is not defined
const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。1
2
3
4if(true) {
console.log(max); // max is not defined
const max = 5;
}
对于符合类型的变量,变量名不指向数据,而是指向数据所在的地址;const
命令只是保证变量名指向的地址不变,并不保证该地址的的数据不变;1
2
3
4const a = [];
a.push('hello');
a.length = 0;
a = ['Dave'] // 报错
上述代码中,常量a是一个数组,这个数组本身是可写的,但是如果将梁一个数组赋值给a,就会报错。
ES5只有两种声明变量的方法:var和function
ES6有:var、function、let、const、import和class;共6种声明变量的方法。
4.全局对象的属性
全局对象是最顶层的对象,在浏览器环境值的是Window对象,在Node.js指的是global对象。
ES5中,全局对象的属性与全局变量是等价的。
ES6中,var和function声明的全局变量,依旧是全局对象的属性;而let、congst、class声明的全局变量,不属于全局对象的属性。1
2
3
4
5var a = 1;
window.a; // 1
let b = 1;
window.b; // undefined