ES6基础知识

#ES6基础知识点
JavaScript的组成:ECMAScript(核心)、DOM(文档对象模型)、BOM (浏览器对象模型)
ECMAScript,规定了语言的组成部分:语法、类型、语句、关键字、保留字、操作符、对象。我们现在使用的是ES5,也就是ECMAScript的第5版本。顾名思义,ES6就是ECMAScript的第6版本,在ES5的基础上,在保证向下兼容的前提下,提供大量新特性。

let const命令

1. let

let用于声明变量,用法类似于var
let与const的区别如下:

1.1 let声明的变量只在let命令所在的代码块中有效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{var a = 10;let b = 1;}
console.log(a)//==>10
console.log(b)//==>报错:b is not defined

典型案例:
for(let i = 0;i < 10;i++){}
console.log(i);//==>i is not defined

var a = new Array(10);
for(var i = 0;i < 10;i++){
a[i] =function(){
console.log(i);
}
}
a[6]();//==>10 变量i是var声明的,在全局范围内都有效,每次循环,新的i会覆盖掉旧的i
1.2 不存在变量提升,使用let时,一定要在变量声明后使用,否则会报错
1
2
3
console.log(foo);//===>ReferenceError:foo is not defined
let foo = 2;
<notice>:此时使用typeof不再是一个百分之百安全操作
1.3 暂时性死区(TDZ)
1
2
3
4
5
6
7
8
9
10
11
12
//只要块级作用域内存在let命令,它所声明的变量就绑定在这个区域,不再受外部的影响
var tmp = 123;
if(true){
tmp = 'abc';
let tmp;
}
//如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域

function bar(x = y,y = 2){
return [x,y];
}
bar();
1.4 不允许重复声明
1
2
3
4
5
6
7
8
9
10
11
12
13
let不允许在相同作用域内重复声明同一个变量
function(){
let a = 10;
let a = 8;
}

function(arg){
let arg;
}

function(arg){
{let arg;}
}

2. const

const 用来声明常量,一旦声明,其值就不能改变

1
2
const PI = 3.14;<br/>
PI = 3;//==>TypeError:'PI' is read-only

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

2.2 const 只声明不赋值就会报错;SyntaxError:missing = in const declaration

2.3 const 作用域与let命令相同:只在声明所在的块级作用域内有效

2.4 const 也存在暂时性死区,只能在声明后使用

2.5 const 不可重复声明常量

2.6 对于复合类型的变量,变量名指向地址,不指向数据

1
2
3
4
5
6
7
const foo = {};
foo.prop = 123;
console.log(foo.prop);//==>123

想对对象冻结,使用 Object.freeze,冻结对象本身,冻结对象的属性
const foo = Object.freeze({});
foo.prop = 123;//==>不起作用,冻结的对象,添加新属性不起作用




模板字符串

<html></html> 传统的写法就是用+进行拼接,并且还要考虑到单引号与双引号的交替以及转义,并且不能换行
模板字符串直接使用反引号 `` 可以定义多行代码,并且嵌入变量

使用模板字符串的注意如下:

2.1 ``转义 +``
2.2 多行字符串,所有的空格以及缩进都会被保留在输出中
2.3 嵌入变量,需要将变量名写在${}
1
2
3
4
function(name,value){
throw new Error('name:'+name+';value:'+value);//以前写法
throw new Error(`name:${name};value:${value}`);//模板字符串写法
}
2.4 调用函数
1
2
3
4
function fn(){
return 'nihao';
}
`everyone,${fn()} yiyi`//==>everyone,nihao yiyi
2.5 ${}大括号中的值不是字符串,按照一般的规则转为字符串

对象 – toString()

方法 – 执行方法后得到的值




变量的解构赋值

按照一定模式,从数组和对象中提取值,对变量进行赋值。类似于“模式匹配”

1. 数组解构赋值

1
2
3
4
5
let [a, b] = [1, 2] // let a = 1; let b = 2;
let [a, b, [c, d]] = [1, 2, [3, 4]] // let a = 1; b = 2; let c = 3; let d = 4;
let [ , , third] = ["foo", "bar", "baz"]; // let third = "baz";
let [x, , y] = [1, 2, 3]; // let x = 1; y = 3;
let [head, ...tail] = [1, 2, 3, 4]; // let head = 1; let tail = [2, 3, 4];
1.1 解构不成功,值就会变成undefined
1
let [x, y, ...z] = ['1'] // let x = '1'; let y = undefined; let z = [];
1.2 如果等号的右边不是数组,将会报错。
1
2
3
4
5
6
7
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

1.3 解构赋值允许指定默认值

只有当一个数组成员严格等于(===)undefined,默认值才会生效

1
2
3
let [x = 1] = []; // x = 1
let [x, y = 'aaa'] = [1, undefined]; // x = 1; y = 'aaa'
let [x = 1] = [null]; // x = 1

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值

1
2
3
4
5
function f() {
console.log('aaa');
}

let [x = f()] = [1];

默认值可以引用解构赋值的其他变量,但该变量必须已经声明

1
2
3
4
let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined

2. 对象解构赋值

1
let {a, b} = {a: 1, b: 2} // a = 1; b = 2;

数组的解构与对象解构的不同:数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值

1
let {sin, cos} = Math

2.1 变量名和属性名不一致
1
let {a: m, b: n} = {a: 1, b: 2} // m = 1; n = 2;

a,b其实是匹配的模式,而真正的变量是m,n
以前所写的let {a, b} = {1, 2}其实是let {a: a, b: b} = {a: 1, b: 2}的缩写

2.2 嵌套解构
1
2
3
4
5
6
7
8
9
10
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};

let { loc, loc: { start }, loc: { start: { line }} } = node;

上面代码有三次解构赋值,分别是对loc、start、line三个属性的解构赋值。注意,最后一次对line属性的解构赋值之中,只有line是变量,loc和start都是模式,不是变量

1
2
3
4
let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
2.3 对象解构也有默认值,默认值起作用的条件也是严格等于(===)undefined时
1
let {x: y = 5, z: m = 1, w = 3} = {null}

3. 解构注意的点⚠️

3.1 将一个已经声明的变量用于解构赋值
1
2
3
let x;
{x} = {x: 1}; // 会报错,因为{x}会被认为是一个代码块
({x} = {x: 1});
3.2 解构赋值允许等号左边的模式之中,不放置任何变量名
1
2
3
({} = [true, false]);
({} = 'abc');
({} = []);

不会报错,可以执行,但是没有任何意义

3.3 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构
1
2
3
let obj ={};
let arr = [];
let {name: obj.name, index: arr[0]} = {name: 'zhaoshijuan', index: 1}
1
2
3
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
// first = 1; last = 3;

除了数组,对象可以解构赋值,还有字符串,布尔值,数值,函数参数等的解构赋值,需要大家自己去学习。




箭头函数

定义函数: var f = v => v;相当于: var f = function(v){return v;}

1. 无参数或者需要多个参数,使用圆括号代表参数部分
1
2
3
4
var f = () =>5;
相当于:var f = function(){return 5;}
var f = (num1,num2) => num1+num2;
相当于:var f = function(num1,num2){return num1+num2;}
2. 代码块部分多于一行代码,使用大括号包裹起来
1
var f = (num1,num2) => {return num1+num2;}
3. 大括号是被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号
1
var getTempItem = id => ({id:id,name:'Temp'});
4. 作用:简化回调函数
1
2
var result = values.sort(function(a,b){return a-b;});
var result = values.sort((a,b) => a-b);
5. rest参数与箭头函数的结合
1
2
3
4
5
const numbers = (...nums) => nums;
numbers(1,2,3,4,5,6);//==>[1,2,3,4,5,6]

const headAndTail = (head,...tail) => [head,tail]
headAndTail(1,2,3,4,5);//==>[1,[2,3,4,5]]

⚠️注意:

1⃣️函数体内的this对象就是定义时所在的对象,而不是使用时所在的对象

2⃣️不可以当做构造函数

1
2
3
4
5
6
7
8
9
10
function foo(){
return () => {
return () =>{
return () =>{
console.log('id',this.id);
}
}
}
}
//这串代码只有一个this,也就是foo的this,箭头函数是没有this的,所以就不能当做构造函数<br/>

3⃣️不可以使用arguments对象,该对象在函数体内不存在,如果要使用,可以使用rest参数代替

//除了this,arguments、super、new.target在箭头函数中也是不存在的




Symbol

Symbol 独一无二的值,是js语言的第7种数据类型 (Undefined,Null,Boolean,String,Number,Object)

1. 使用注意

1.1 symbol值通过symbol函数生成,symbol值不是对象
1
2
let s = Symbol();
console.log(typeof s);//==>symbol
1.2 可以接受一个字符串作为参数,用于对symbol实例的描述
1
2
3
var s1 = Symbol('foo');
console.log(s1);//==>Symbol(foo)
console.log(s1.toString);//==>'Symbol(foo)'
1.3 Symbol值不能与其他类型的值进行运算
1.4 Symbol值可以显式转为字符串
1
2
3
var sym = Symbol('my symbol');
console.log(String(sym));//==>'Symbol('my symbol')'
console.log(sym.toString());//==>'Symbol('my symbol')'
1.5 Symbol值可以转为Boolean值
1
2
3
var sym1 = Symbol();
console.log(Boolean(sym1));//==>true
console.log(Boolean(!sym1));//==>false

2. 作为属性名的Symbol

每一个symbol值都是不相等的,因此不会出现同名的属性

1
2
3
4
var mySymbol = Symbol();
var a = {};
a[mySymbol] = 'helloWorld';
var a = {[mySymbol]:'helloWorld'};
2.1 Symbol值作为对象属性名时不能使用点运算符,要放在方括号中
1
2
3
4
5
6
7
8
9
10
var mySymbol = Symbol();
var a = {};
a.mySymbol = 'helloWorld';
console.log(a[mySymbol]);//==>undefined
console.log(a['mySymbol']);//==>'helloWorld'
//此时,a的属性名相当于是一个字符串,而不是一个Symbol值

let s = Symbol();
let obj = {[s]:function(arg){...}}
console.log(obj[s]);//==>function(arg){...}
2.2 Symbol类型还可用于定义一组常量,保证这组常量的值都是不相等的。
2.3 消除魔术字符串

魔术字符串:在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或数值
使用同一个Symbol

1
2
3
4
Symbol.for()
var a = Symbol.for('foo');
var b = Symbol.for('foo');
console.log(a === b);//==>true




Promise

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

1. Promise特点

1.对象的状态不受外界的影响,三种状态:pending(进行中),fufilled(已成功),rejected(已失败)

2.一旦状态改变,就不会再变。状态的两种改变:pending—>fulfilled,pending—>rejected

2. promise对象是一个构造函数,用来生成promise实例

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署

1
2
3
4
5
6
7
8
9
10
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
//resolve 将Promise对象的状态pending--->fulfilled,
//reject 将Promise对象的状态pending--->rejected

3. then方法指定resolved和rejected回调,then方法返回的是一个新的Promise实例,因此可以使用链式写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
promise.then(function(value){
console.log('sucess resolved')
},function(error){
console.log('failure rejected(可选)')
});

function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
//链式写法
promise.then(function(value){
console.log('sucess resolved')
},function(error){
console.log('failure rejected(可选)')
}).then(function(){
console.log('then==>sucess');
},function(){
console.log('then==>failure');
});

4. Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved');
});
console.log('Hi!');
// Promise
// Hi!
// resolved

var p1 = new Promise(function (resolve, reject) {
console.log('Promise===>p1');
});
var p2 = new Promise(function (resolve, reject) {
console.log('Promise===>p2');
resolve(p1);
})
console.log('outside');

p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行

5. catch方法是用于指定发生错误时的回调函数

1
2
3
4
5
6
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});

getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

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
var promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test

//如果Promise状态已经变成resolved,再抛出错误是无效的。
var promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok

// Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
var promise = new Promise(function(){
console.log('p1==>sucess')
},function(){
throw new Error('test');
});
promise.then(function(){
console.log('p2==>sucess');
throw new Error('test');
}).then(function(){
console.log('p3==>sucess');
}).catch(function(error){
console.log(error);//捕获前三个Promise实例的错误
});

6. Promise.all() 用于将多个Promise实例包装成一个新的Promise实例

1
2
3
4
5
6
7
8
9
10
11
12
var p1 = new Promise(function(resolve,reject){
console.log('p1');
resolve('promise1');
}),p2 = new Promise(function(resolve,reject){
console.log('p2');
resolve('promise2');
}),p3 = new Promise(function(resolve,reject){
console.log('p3');
resolve('promise3');
});
var p = Promise.all([p1,p2,p3]);
console.log(p);

p1、p2、p3只要有一个rejected,p就会变成rejected;只有全部是fulfilled,p才会变成fulfilled

7. Promise.race() 将多个Promise实例包装成一个新的Promise实例

1
2
3
4
5
6
7
8
9
10
var p = Promise.race([p1,p2,p3]);
//只要p1,p2,p3有一个率先改变状态,p就会跟着改变。率先改变的Promise实例的返回值,就传递给p的回调函数
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));

8. Promise.reject() 将对象转为Promise对象,并且状态为rejected

9. Promise.resolve() 将对象转为Promise对象,并且状态为resolved




Module语法

前言:只介绍module的语法以及使用过程中的注意事项,不会介绍原理,感兴趣的可以自己去研究。

由于原生JavaScript没有模块这一个概念,对于过长的JavaScript文件而言,只能拆分,但是彼此之间又不能建立联系(除了使用window顶层对象,但是这种方法是不提倡的),后期社区执行了一些模块加载方案,主要就是CommonJs与AMD。CommonJs主要应用于服务器端(NodeJs);AMD主要用于客户端(Javascript),其中最常用的就是RequireJs。

ES6的module可以在服务器端,也可以在客户端,而且语法一致。

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上”use strict”「ES5的东西,大家可以自己去看看」。ES6模块中的this指向undefined。

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能

1. export

1
2
3
export var a = 1;
export var b = 'zhaoshijuan';
export var c = true;

等价于

1
2
3
4
var a = 1;
var b = 'zhaoshijuan';
var c = true;
export {a, b, c}
1.1 别名
1
2
3
4
5
6
7
8
function v1() { ... }
function v2() { ... }

export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};

使用as关键字,重新命名了函数v1和v2的对外接口。

1.2 export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系
1
2
3
4
5
6
// 报错,直接输出1,1只是一个值,不是接口
export 1;

// 报错,通过变量m,还是输出1
var m = 1;
export m;
1
2
3
4
5
6
7
8
9
10
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};
1.3 export命令可以出现在模块的任何位置,只要处于模块顶层就可以。

如果处于块级作用域内,就会报错,import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

2. import

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

1
2
3
4
5
6
// main.js
import {firstName, lastName, year} from './profile.js';

function setName(element) {
element.textContent = firstName + ' ' + lastName;
}

form后面的是模块的位置,可以是绝对路径,也可以是相对路径。.js的后缀可以省略。

2.1 大括号里面的变量名,必须与被导入模块对外接口的名称相同
2.2 别名使用as
1
import { lastName as surname } from './profile.js';
2.3 import命名输入的变量是只读

如果是一个对象,则对象里面的属性是可写的,但是不利于后期的排错什么。

1
2
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
2.4 如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次
2.5 import命令具有提升效果,会提升到整个模块的头部,首先执行
1
2
3
foo();

import { foo } from 'my_module';

⚠️ES6模块是编译时加载,因此在import是静态执行,不可以使用变量,表达式。动态加载使用import()

1
2
3
4
5
6
7
8
9
10
11
12
13
// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}

参考资料:《ECMAScript 6 入门》阮一峰译(http://es6.ruanyifeng.com)