let命令

基本用法

ES6新增了let命令,用法类似于var,但是所声明的变量只在let所在的代码块生效

1
2
3
4
5
6
{
let a = 1;
var b = 2;
}
console.log(a); // => ReferenceError: Can't find variable: a
console.log(b); // => 2

for循环中的let

1
2
3
4
for (let i = 0; i < 3; i++) {
console.log(i); // => 0, 1, 2
}
console.log(i); // => Uncaught ReferenceError: i is not defined

上述代码中的ilet声明,只在for循环体内有效,在循环体外引用则会报错

1
2
3
4
for (var i = 0; i < 3; i++) {
console.log(i); // => 0, 1, 2
}
console.log(i); // => 3

上述代码中的ivar声明,在全局范围内有效,全局只有一个变量i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = [];
for (var i = 0; i < 3; i++) {
a[i] = function() {
console.log(i);
};
}
a[1](); // => 3
-------------------------------------------
var a = [];
for (let i = 0; i < 3; i++) {
a[i] = function() {
console.log(i);
};
}
a[1](); // => 1

当循环中的ilet声明时,当前一轮循环中的i只在本轮循环中有效。每一次循环中的i其实是一个新的变量,JavaScript引擎内部会记住上一轮循环的值,初始化本轮i的值是在上一轮的基础上计算。

for循环设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

1
2
3
4
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i); // => abc, abc, abc
}

不存在变量提升

let所声明的变量一定要在声明后使用,否则会报错

1
2
3
4
5
console.log(i);
let i = 1; // => Uncaught ReferenceError: i is not defined
------------------------------------------------------
console.log(i);
var i = 1; // => undefined

暂时性死区(temporal dead zone, TDZ)

ES6规定:如果区块中存在letconst命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域,只要在声明之前就使用这些变量,就会报错

1
2
3
4
5
{
// TDZ
let a;
// code...
}

变量一定要在声明之后使用

1
2
3
4
5
6
7
8
9
10
11
12
var a = 1;
if (true) {
a = 'abc';
typeof a; // => Uncaught ReferenceError: a is not defined
console.log(a); // => Uncaught ReferenceError: a is not defined

let a;
console.log(a); // => undefined

a = 'abc';
console.log(a); // => abc
}

一些隐蔽的死区:

  • x的默认值等于y,而y还没有声明

    1
    2
    3
    4
    (function f(x = y, y = 2) {
    return [x, y];
    })();
    // Uncaught ReferenceError: y is not defined
  • x的声明语句还没执行完之前就尝试获取x的值

    1
    2
    3
    let x = x;          // => Uncaught ReferenceError: y is not defined
    ------------------------------------------------
    var x = x; // 不报错

不允许重复声明

let不允许在相同作用域内重复声明同一个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function f() {  
var a = 1;
let a = 1;
}
// Uncaught SyntaxError: Identifier 'a' has already been declared
-------------------------------------------
function f() {
let a = 1;
let a = 1;
}
// Uncaught SyntaxError: Identifier 'a' has already been declared
-------------------------------------------
function f() {
let a = 1;
var a = 1;
}
// Uncaught SyntaxError: Identifier 'a' has already been declared
-------------------------------------------
function f() {
var a = 1;
var a = 1;
}

不能在函数内部重新声明参数

1
2
3
4
5
6
7
8
9
10
function f(a) {  
let a = 1;
}
// Uncaught SyntaxError: Identifier 'a' has already been declared
-------------------------------------------
function f(a) {
{
let a = 1;
}
}

const命令

基本用法

const命令声明一个只读的常量。一旦声明,常量的值就不能改变

1
2
3
4
const PI = 3.1415;
console.log(PI); // => 3.1415

PI = 3; // => Uncaught TypeError: Assignment to constant variable.

const一旦声明常量,必须立即初始化,不能留到以后赋值

1
const a;                // => Uncaught SyntaxError: Missing initializer in const declaration

const只在声明所在的块级作用域内有效

1
2
3
4
if (true) {
const a = 1;
}
console.log(a); // => Uncaught ReferenceError: a is not defined

const命令不存在变量提升,存在TDZ,只能在声明后使用

1
2
3
4
if (true) {
console.log(a); // => Uncaught ReferenceError: a is not defined
const a = 1;
}

const不可重复声明

1
2
var a = 1;
const a = 1; // => Uncaught SyntaxError: Identifier 'a' has already been declared

本质

const实际上保证的不是变量的值不得改动,而是变量指向的那个内存地址不得改动

  • 对简单类型的数据,值保存在变量指向的内存地址中,因此等同于常量

  • 对复合类型的数据,变量指向的内存地址保存的只是一个指针,它指向的数据结构是不是可变则完全不可控

1
2
3
4
5
const obj = {};
obj.index = 1;
console.log(obj.index); // => 1

obj = {}; // => Uncaught TypeError: Assignment to constant variable.

想将对象冻结,可以使用Object.freeze()方法

1
2
3
4
5
6
7
8
'use strict'
const obj = Object.freeze({});
obj.index = 1;
console.log(obj.index); // => Uncaught TypeError: Cannot add property index, object is not extensible
-------------------------------
const obj = Object.freeze({});
obj.index = 1;
console.log(obj.index); // => undefined

块级作用域

ES6的块级作用域

letconst实际上为JavaScript增加了块级作用域

1
2
3
4
5
6
7
(function () {
let a = 1;
if (true) {
let a = 10;
}
console.log(a); // => 1
})();

ES6允许块级作用域的任意嵌套。外层作用域无法读取内存作用域的变量,内层作用域可以定义外层作用域的同名变量

1
2
3
4
5
6
{
{
let a = 1;
{ let a = 1;}
}
}

块级作用域与函数声明

为减轻不兼容问题,ES6规定:

  • 允许在块级作用域内声明函数
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
  • 同时,函数声明还会提升到所在的块级作用域的头部
1
2
3
4
5
6
7
8
9
10
11
12
function f() {
console.log('asdfgh');
}
(function () {
if (false) {
function f() {
console.log('123456');
}
}
f();
})();
// Uncaught TypeError: f is not a function