ECMAScript 6 基础知识

一、let和const命令

1. let命令
基本用法

ES6新增了let命令,用来声明变量。用法类似于var,但是也存在新特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1)let所声明的变量,只在let命令所在的代码块有效。适用于for循环。

{let a = 10;var b = 1;}
//a a is not defined
//b 1

2)不存在变量提升。
console.log{bar};
let bar = 2;
//报错ReferenceError:bar is not defined

3)暂时性死区。
在代码块内,使用let命令变量之前,该变量都是不可用的。
var tmp = 123;
if(true){
tmp = 'abc'; //ReferenceError
let tmp;
}

4)不允许重复声明。
function(){let a =10;let a = 1;} //报错
块级作用域

let实际上为JavaScript新增了块级作用域。外层作用域无法读取内层作用域的变量,内层作用域可以定义外层作用域的同名变量。

1
2
3
4
5
6
7
let foo = 1;
{
let foo = 2; //定义同名变量
let bar = "one";
}
console.log{foo}; //1
console.log(bar); //报错

块级作用域的出现,实际上使得广泛应用的立即执行函数表达式(IIFE)不再必要了。

2. const命令
基本用法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1)const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo; //SyntaxErroe:Missing initializer in const declaration
2)块级作用域
只在声明所在的块级作用域内有效。
3)暂时性死区
在代码块内,使用let命令声明变量之前,该变量是不可用。

if(true){
console.log(MAX); //ReferenceError const MAX = 5;
}
4)不允许重复声明
var message = 'Hello!';
let age = 25;
//以下两行都会报错
const message = 'Goodbye!';
const age = 30;

用途:为了防止意外修改变量,比如,引入库名,组件名等

let、const与var的区别:

1
2
3
a. let、const有块级作用域的概念,只要是定义的变量,就是一个块级作用域;var只有在函数中才有作用域;
b. let、const不允许重复声明;
c. let和var声明的变量,都可以再修改,而const定义的是常量,值是不可以修改的(对象除外,对象的属性可以修改)

作用域的概念:ES5有两个作用域:全局和局部

ES6有了块作用域的概念:块作用域,通俗的理解,就是一个大括号,就是一个块作用域;

ES6 声明变量的六种方法

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

顶层对象的属性

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

1
2
3
4
5
window.a = 1;
a // 1

a = 2;
window.a // 2

上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。

顶层对象的属性与全局变量挂钩,是JavaScript 语言最大的设计败笔之一。ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

1
2
3
4
5
6
7
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。

globalThis 对象

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

1
2
3
浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

  • 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
  • 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。

综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);

// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};

现在有一个提案,在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。

二、解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructing)。

1
2
3
4
5
6
7
8
以前,为变量赋值,只能直接指定值。

let a = 1;
let b = 2;
let c = 3;
ES6 允许写成下面这样。

let [a, b, c] = [1, 2, 3];

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果结构不成功,变量的值就等于undefined。另一种情况是不完全解构,即等号左边的模式只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

数组的解构赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let [a,b,c] = [1,2,3];

1)不完全解构:

let [a,[b],d] = [1,[2,3],4]; //a=1;b=2;d=4

2)集合解构:

let[head,...tail] = [1,2,3,4]; //head=1;tail=[2,3,4]

3)默认值(当匹配值严格等于undefined时,默认值生效):

let [x,y='b'] = ['a']; //x = 'a',y = 'b'

4)默认值也可以为函数:

function f(){ console.log('aaa'); }
let [x=f()] = [1];
对象的解构赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1)对象的属性没有次序,变量必须与属性同名,才能取到正确的值

let {foo,bar} = {foo:'aaa',bar:'bbb'}; //foo='aaa';bar='bbb'

2)如果变量名与属性名不一致,必须写成下面这样。

var {foo:baz} = {foo:'aaa',bar:'bbb'}; // baz='aaa'

3)这实际上说明,对象的解构赋值是下面形式的简写。

let {foo:foo,bar:bar} = {foo:'aaa',bar:'bbb'};

4)嵌套结构

let obj = {p:['Hello',{y:'World'}]};
let {p:[x,{y}]} = obj; // x='Hello';y='World'

5)默认值(默认值生效的条件是,对象的属性值严格等于undefined)
var {x:y=3} = {}; // y=3

对象的解构也可以指定默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
字符串的解构赋值
1
2
3
4
5
6
7
1)解构时,字符串被转换成了一个类似数组的对象。

const [a,b,c,d,e] = 'hello'; //a=h;b=e;c=l;d=l;e=o

2)也可以对数组的属性解构

let {length:len} = 'hello'; // len=5
数值和布尔值的解构赋值
1
2
3
4
解构时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString:s} = 123; // s===Number.prototype.toString true
let {toString:s} = true; // s===Boolean.prototype.toString true
函数参数的解构赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1)基本语法

function add([x,y]){return x+y;}
add([1,2]);

2)默认值

function move({x=0,y=0}){
return [x,y];
}
move({x:3,y:8}); //[3,8]
move({x:3}); //[3,0]
move({}); //[0,0]
move(); //[0,0]
常见用途
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
1)交换变量的值

let x = 1;
let y = 2;
[x,y] = [y,x];

2)从函数返回多个值

function example(){
return [1,2,3];
}
let [a,b,c] = example();

3)函数参数的定义

function f([x,y,z]){...}
f([1,2,3]);

4)提取JSON数据

let jsonData = {id:42,status:'OK',data:[867,5309]};
let {id,status,data:number} = jsonData;

5)输入模块的指定方法

const {SourceMapConsumer,SourceNode} = require("source-map");

6)函数参数的默认值

jQuery.ajax = function(url,{
async = true,cache = true,global = true,
beforeSend = function(){},
complete = function(){},
//...more config
}){//...do stuff};

指定参数的默认值,就避免了在函数体内部再写var foo = config.foo||'default foo';这样的语句。

7)遍历map结构

var map = new Map();
map.set('first','hello');
map.set('second','world');
for(let [key,value] of map){
console.log(key + "is" + value);
}

三、字符串、正则、对象、函数、数组等扩展

字符串的扩展

字符串的遍历器接口

1
2
3
4
5
6
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"