ES6学习笔记——let和const

ES6学习笔记——let和const

letconst 是es6新增的用于声明变量的命令 。
let 用于可以代替 var ,用于声明作用域为该代码块的变量。
const 用于声明作用域为该代码块的常量。

es5和es6声明变量的几种方式

es5

  • var
  • function

es6

  • var

  • function

  • let

  • const

  • import

  • class

let

作用域

ES6新增了 let 命令用于声明变量,只在所在代码块内有效。

实例:

1
2
3
4
5
6
{
let a = 1;
var b = 2;
}
a // ReferenceError: a is not defined.
b // 2

实例:

1
2
3
4
5
6
7
// 这里的i的作用域仅仅在循环体内
for (let i = 0; i < 10; i++) {

}

i
// ReferenceError: i is not defined

实例:

1
2
3
4
5
6
7
8
9
var a = [];

// 这里var声明的i为全局变量
for (var i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
};
}
a[6](); // 10
1
2
3
4
5
6
7
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
};
}
a[6](); // 6

for 循环中: () 内和 {} 内不是同一个作用域, () 位于 {} 的父作用域,孤儿 {} 内部可以再次声明已经在 () 中声明过的变量。

1
2
3
4
5
6
7
8
for (let i = 0; i < 3; i++) {
// 这里重新声明了i覆盖了上面的声明
let i = 123;
console.log(i);
}
// 123
// 123
// 123

另外:在同一个作用域内不可以使用 let 重复声明同一个变量。

没有变量提升

var 有变量提升。
let 没有变量提升。

故而, let 需要先声明后使用。

实例:

1
2
3
4
5
6
7
// var,这里使用var声明的foo会被提升到顶部,但是对于foo的赋值没有被提升到顶部,故而可以获取到foo但是为undefined
console.log(foo); // 输出undefined
var foo = 2;

// let,这里使用let声明的bar不会被提升到顶部,会报引用错误
console.log(bar); // 报错ReferenceError
let bar = 2;

暂时性死区(TDZ)

只要块级作用域内存在 let ,那么它所声明的变量就绑定了这个作用域且不受外部影响。

实例:

1
2
3
4
5
6
var tmp = 123;

if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}

这里很奇怪的一点就是:js代码是自上往下顺序执行的,代码块内如何提前知道下面有let声明了某个在let之前就被操作了的变量并在let之前就抛出错误呢?

找到了stackoverflow的说法:let和const也是存在变量提升的,只是不像var变量提升的时候会被初始化为undefined,let和const会一直保持未初始化的状态。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
if (true) {
// TDZ开始
temp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError

let tmp;
// TDZ结束
console.log(tmp); // undefined

tmp = 123;
console.log(tmp); // 123
}

正因为 let 的TDZ特性,使得很多在 let 声明一个变量之前对于这个变量做的操作失效。

故而,在一个代码块内,对于一个变量,一定要先使用 let 声明后使用。

实例:隐蔽的死区

1
2
3
4
5
function bar(x = y, y = 2) {
return [x, y];
}

bar(); // ReferenceError

这里y还没声明就赋值给了x,也就是y还没被声明就被使用了,此时为TDZ故而报错。

实例:

1
2
3
4
5
// 不报错
var x = x;

// 报错
let x = x;

软神的这句话可能有误:

ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

总而言之:需要先let声明后使用,否则就报错。

不允许重复声明

不允许使用 let 在同一作用域内多次声明一个变量,否则报错。

块作用域

es5作用域

es5的作用域:只有全局作用域和函数内作用域。

实例:函数体内部的声明覆盖了全局声明

1
2
3
4
5
6
7
8
9
10
11
var tmp = new Date();

function f() {
console.log(tmp);
if (0) {
var tmp = 'abc';
}
}

f();
// undefined

这里编写代码的本意是在 console.log 这行使用外部 tmp ,在 if 循环体内部重新声明变量 tmp ,但是结果却是 undefined
原因是循环体内部对于 tmp 的声明被提升到了函数的顶部,故而 console.log 打印的是已经被在函数内声明的 tmp

实例:计数变量泄露为全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s = 'hello';

for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
// h
// e
// l
// l
// o
// undefined

console.log(i);
// 5

这里的 i 最终被计算成了5,并且是一个全局变量,造成了变量泄露。

es6作用域

es6的作用域: let 为js提供了块作用域, let 声明的变量作用域仅在其所处的代码块内,并且外层代码块不受内层代码块的影响。

实例:

1
2
3
4
5
6
7
8
9
function f1() {
// 这里声明的n的作用域在函数内部
let n = 5;

if (true) {
let n = 10; // 这里声明的n的作用域仅在if内部
}
console.log(n); // 5
}

实例:es6允许块作用域任意层嵌套, 内层可以访问外层,外层无法访问内层。

1
2
3
4
5
6
7
{
let num = 1; {
let num = 2; {
let num = 3;
}
}
}

块作用域可以替代IIFE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// IIFE写法
(function() {
var tmp = 123;
console.log(tmp);
// 123
})();
// 外部不可访问

// 块作用域替代
{
let tmp = 123;
console.log(tmp);
// 123
}
// 外部不可访问

块作用域和函数声明

那么函数可以在块作用域内声明吗?

es5:只能在全局声明或者函数内作用域声明,但是浏览器不会报错。

es6:允许在块作用域内声明函数。

实例:下面的代码在es5中运行会得到 I am inside! ,因为if内部的重新声明会被提升到函数顶部从而覆盖了外部的声明。
而如果在es6中运行理论上会得到 I am outside! 但是实际上在浏览器中运行都会报错,原因是这段代码在es5和在es6中的运行结果截然相反,故而会导致严重的问题,为了保证对es5的兼容会在es6中报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function f() {
console.log('I am outside!');
}

(function() {
if (false) {
// 重复声明一次函数f
function f() {
console.log('I am inside!');
}
}

f();
}());

故而不要在块作用域内部声明函数或者如果要声明也要携程函数表达式。

1
2
3
4
5
6
7
8
9
// 不建议
{
function f() {}
}

// 如果要在块内部声明函数则用表达式
{
let f = function() {}
}

es6的块作用域必须有大括号 {} ,否则js不认为其是块作用域。
实例:

1
2
3
4
5
6
7
// 没有写大括号,报错
if (1) let x = 1;

// 正确写法
if (1) {
let x = 1;
}
1
2
3
4
5
6
'use strict';

// 这个函数作用域仅在if内部
if (1) {
function f() {}
}

const

基本用法

const 声明一个只读的常量,其指向一个内存空间,内存空间内容不可改变。
故而 const 声明时就要赋值,因为一旦声明之后就无法改变。

const 的作用域和 let 一样:只在其所声明的块作用域内有效。
const 也存在TDZ,也需要先声明并赋值后使用。
const 也不可以重复声明。

const的本质

const 实质上是保证 const 变量指向的内存空间的内容不变。
对于基本数据类型而言:指向的内存空间就是保存了基本类型的数据(布尔,字符串,数字),故而一旦使用 const 声明了基本数据类型就无法改变。
对于符合数据类型而言:指向的内存空间保存了这个复杂类型的指针,而指针又指向了另外一个或者多个内存空间,这里的内存空间才是真正保存了复杂类型下的基本类型的值。

实例:

1
2
3
4
5
6
7
8
9
const person = {};

person.name = 'mason';
person.age = 22;
// 修改person对象的属性均成功

person = {};
// 报错
// 这里给person赋了一个新的值{},这里的{}内存地址和原来的不一样故报错

实例:

1
2
3
4
5
6
7
const arr = [];
arr.push('a');
// 成功

arr = ['b'];
// 报错
// 这里给arr赋值了新的内存空间故而报错

顶层对象的属性

浏览器中的顶层对象是 window 对象。
node环境中的顶层对象是 global 对象。

在es5中:顶层对象的属性和全局变量等价。

1
2
3
4
5
window.person = 'mason';
person // 'mason'

person // 'mason'
window.person // 'mason'

在es6中: varfunction 声明的仍然是顶层对象的属性(或者说全局变量),而 let , const , class 声明的全局变量不属于顶层对象的属性。

1
2
3
4
5
6
7
8
// 这两种写法等价
var name = 'mason';
window.name = 'mason';

// let声明的变量不属于顶层对象的属性
let name = 'mason';
window.name
// undefined

globalThis对象

上面说了js在浏览器环境和在node环境中的顶层对象不同,或者说“在各个js的实现里面,顶层对象不统一。”

为了能让代码能适应多个实现的环境,在es2020里面引入了 globalThis 对象,该对象存在于所有环境,可以通过其拿到顶层对象。

浏览器环境
1
2
3
4
// 全局作用域下,以下均为true
window === this
window === globalThis
this === globalThis
node
1
2
3
4
// 全局作用域下,以下均为true
global === this
global === globalThis
this === globalThis

评论