ES6 简介

ES 的全称是 ECMAScript,它是由 ECMA 国际标准化组织制定的一项脚本语言的标准化规范,ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。

var、let、const

let

  • let 声明的变量只在所处的块级作用域有效
1
2
3
4
5
6
if (true) {
let num = 100;
var abc = 200;
}
console.log(abc);
console.log(num);
  • 不存在变量提升
1
2
console.log(a);
let a = 100;
  • 暂时性死区
1
2
3
4
5
var num = 10;
if (true) {
console.log(num); // ReferenceError
let num = 20;
}

经典面试题

使用 let 时,每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的
函数执行时输出的是自己循环产生的块级作用域下的值

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

console.log('-------------');
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
};
}
arr[0](); // 0
arr[1](); // 1

const

  • 具有块级作用域
1
2
3
4
5
6
7
8
9
if (true) {
const a = 10;
if (true) {
const a = 20;
console.log(a);
}
console.log(a);
}
console.log(a);
  • 声明常量时必须赋值
1
const a; // Uncaught SyntaxError: Missing initializer in const declaration
  • 常量赋值后,值不能修改
1
2
3
4
5
6
const PI = 3.14;
PI = 100; // Uncaught TypeError: Assignment to constant variable.
const ary = [100, 200];
ary[0] = 123;
ary = [1, 2];
console.log(ary);

let、const、var 的区别

varletconst
全局作用域块级作用域块级作用域
变量提升不存在变量提升不存在变量提升
值可更改值可更改值不可更改

解构赋值

按照一定的模式,从数组或对象中提取值,将提取出来的值赋值给另外的变量

数组解构

1
2
3
4
5
6
let ary = [1, 2, 3];
let [a, b, c, d] = ary;
console.log(a);
console.log(b);
console.log(c);
console.log(d); // undefined

对象解构

1
2
3
4
5
6
7
8
let person = { name: 'lisi', age: 30, sex: '男' };
let { name, age, sex } = person;
console.log(name);
console.log(age);
console.log(sex);

let { name: myName } = person;
console.log(myName);

箭头函数

1
2
3
4
const fn = () => {
console.log(123);
};
fn();
  • 如果函数体中只有一句代码 并且代码的执行结果就是函数的返回值 函数体大括号可以省略
1
2
3
const sum = (n1, n2) => n1 + n2;
const result = sum(10, 20);
console.log(result);
  • 如果形参只有一个 形参外侧的小括号也是可以省略的
1
2
const fn = (v) => console.log(v);
fn(20);
  • 箭头函数不绑定 this 箭头函数没有自己的 this 关键字
1
2
3
4
5
6
7
8
9
10
// 如果在箭头函数中使用 this, this 关键字将指向箭头函数定义位置中的 this
function fn() {
console.log(this);
return () => {
console.log(this);
};
}
const obj = { name: 'zhangsan' };
const resFn = fn.call(obj); // { name: 'zhangsan' }
resFn(); // { name: 'zhangsan' }

经典面试题

1
2
3
4
5
6
7
8
var age = 100;
var obj = {
age: 20,
say: () => {
console.log(this.age);
},
};
obj.say(); // 100

剩余参数

  • 将一个不定数量的参数表示为一个数组
1
2
3
4
5
6
7
const sum = (...args) => {
let total = 0;
args.forEach((item) => (total += item));
return total;
};
console.log(sum(10, 20));
console.log(sum(10, 20, 30));

剩余参数和解构搭配

1
2
3
4
let ary1 = ['张三', '李四', '王五'];
let [s1, ...s2] = ary1;
console.log(s1);
console.log(s2);

数组的扩展

  • 扩展运算符可以将数组拆分成以逗号分隔的参数序列
1
2
3
4
let ary = ['a', 'b', 'c'];
// ...ary // "a", "b", "c"
console.log(...ary);
console.log('a', 'b', 'c');
  • 扩展运算符应用于数组合并
1
2
3
4
5
6
7
8
9
10
11
12
let ary1 = [1, 2, 3];
let ary2 = [4, 5, 6];
// ...ary1 // 1, 2, 3
// ...ary1 // 4, 5, 6
let ary3 = [...ary1, ...ary2];
console.log(ary3);

// 合并数组的第二种方法
let ary1 = [1, 2, 3];
let ary2 = [4, 5, 6];
ary1.push(...ary2);
console.log(ary1);
  • 利用扩展运算符将伪数组转换为真正的数组
1
2
3
4
5
6
7
8
9
10
<div>1</div>
<div>2</div>
<div>3</div>
<script>
var oDivs = document.getElementsByTagName('div');
console.log(oDivs);
var ary = [...oDivs];
ary.push('a');
console.log(ary);
</script>

Array.from()

  • 将类数组或可遍历对象转换为真正的数组
1
2
3
4
5
6
7
8
let arrayLike = {
0: '张三',
1: '李四',
2: '王五',
length: 3,
};
let ary = Array.from(arrayLike);
console.log(ary);
  • 还可以接收第二个参数,类似于数组的 map()方法,将处理后的值返放入返回的数组
1
2
3
4
5
6
7
let arrayLike = {
0: '1',
1: '2',
length: 2,
};
let ary = Array.from(arrayLike, (item) => item * 2);
console.log(ary);

find()

  • 用于找出第一个符合条件的数组成员,如果没有找到返回 undefined
1
2
3
4
5
6
7
8
9
10
11
12
var ary = [
{
id: 1,
name: '张三',
},
{
id: 2,
name: '李四',
},
];
let target = ary.find((item) => item.id == 3);
console.log(target);

findIndex()

  • 用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
1
2
3
let ary = [10, 20, 50];
let index = ary.findIndex((item) => item > 15);
console.log(index);

includes()

  • 表示某个数组是否包含给定的值,返回布尔值
1
2
3
4
5
let ary = ['a', 'b', 'c'];
let result = ary.includes('a');
console.log(result); // true
result = ary.includes('e');
console.log(result); // false

字符串的扩展

模板字符串

  • ES6 新增的创建字符串方式,使用反引号定义,可以解析变量
1
2
3
let name = `张三`;
let sayHello = `Hello, 我的名字叫${name}`;
console.log(sayHello);
  • 模板字符串可以换行
1
2
3
4
5
6
7
8
9
10
11
let result = {
name: 'zhangsan',
age: 20,
};
let html = `
<div>
<span>${result.name}</span>
<span>${result.age}</span>
</div>
`;
console.log(html);
  • 模板字符串中可以调用函数
1
2
3
4
5
const fn = () => {
return '我是fn函数';
};
let html = `我是模板字符串 ${fn()}`;
console.log(html);

startsWith() 和 endsWith()

  • startsWith():表示参数是否在原字符串的头部,返回布尔值
  • endsWith():表示参数是否在原字符串的尾部,返回布尔值
1
2
3
4
5
let str = 'Hello ECMAScript 2015';
let r1 = str.startsWith('Hello');
console.log(r1);
let r2 = str.endsWith('2016');
console.log(r2);

repeat()

  • repeat() 方法表示将原字符串重复 n 次,返回一个新字符串
1
console.log('y'.repeat(5));

Set 数据结构

  • 类似于数组,但是成员的值都是唯一的,没有重复的值
1
2
3
4
5
6
7
8
9
10
const s1 = new Set();
console.log(s1.size); // 0

const s2 = new Set(['a', 'b']);
console.log(s2.size); // 2

const s3 = new Set(['a', 'a', 'b', 'b']);
console.log(s3.size); // 2
const ary = [...s3];
console.log(ary); // ['a', 'b']

实例方法

  • add(value):添加某个值,返回 Set 结构本身
  • delete(value):删除某个值,返回一个布尔值,表示是否删除成功
  • has(value):返回一个布尔值,表示该值是否为 Set 的成员
  • clear():清除所有成员,没有返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const s4 = new Set();
// 向set结构中添加值 使用add方法
s4.add('a').add('b');
console.log(s4.size);

// 从set结构中删除值 用到的方法是delete
const r1 = s4.delete('c');
console.log(s4.size);
console.log(r1);

// 判断某一个值是否是set数据结构中的成员 使用has
const r2 = s4.has('d');
console.log(r2);

// 清空set数据结构中的值 使用clear方法
s4.clear();
console.log(s4.size);

遍历

1
2
3
4
5
// 遍历set数据结构 从中取值
const s5 = new Set(['a', 'b', 'c']);
s5.forEach((value) => {
console.log(value);
});

模块化

ES6 的模块化主要包含如下 3 种用法:

  1. 默认导出与默认导入
  2. 按需导出与按需导入
  3. 直接导入并执行模块中的代码

默认导出

1
2
3
4
5
6
7
8
let n1 = 10;
let n2 = 20;
function show() {}

export default {
n1,
show,
};

默认导入

1
2
3
import m1 from './01.默认导出.js';

console.log(m1); // { n1: 10, show: [Function: show] }

默认导出与默认导入的注意事项

  1. 每个模块中,只允许使用唯一的一次 export default(默认导出),否则会报错!
1
2
3
4
5
6
7
8
9
10
11
12
13
let n1 = 10
let n2 = 20
function show() {}

export default {
n1,
show
}

// SyntaxError: Identifier '.default' has already been declared
export default {
n2
}
  1. 默认导入时的接收可以任意名称,只要是合法的名称即可
1
2
3
4
import m1 from './m1.js'

// 名称不能以数字开头,不合法
import 123m from './m1.js'

按需导出

1
2
3
4
5
6
7
8
export let s1 = 'aaa';
export let s2 = 'ccc';
export function say() {}

// 默认导出和按需导出可共存
export default {
a: 20,
};

按需导入

1
2
3
4
5
6
7
import { s1, s2 as str2, say } from './03.按需导出.js';
import info from './03.按需导出.js';

console.log(s1); // aaa
console.log(str2); // ccc
console.log(say); // [Function: say]
console.log(info); // { a: 20 }

按需导出与按需导入的注意事项

  1. 每个模块可以使用多次按需导出
  2. 按需导入的成员名称必须和按需导出的名称保持一致
  3. 按需导入时,可以使用 as 关键字进行重命名
  4. 按需导入可以和默认导入一起使用

直接导入并执行模块中的代码

如果只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码

1
2
3
4
5
6
7
8
for (let i = 0; i < 3; i++) {
console.log(i);
}

// -----------------分割线---------------------

// 直接导入并执行模块代码,不需要得到模块向外共享的成员
import './05.直接运行模块中的代码.js';

Promise

  1. Promise 是一个构造函数
    • 我们可以创建 Promise 的实例 const p = new Promise()
    • new 出来的 Promise 实例对象,代表一个异步操作
  2. Promise.prototype 上包含一个 .then() 方法
    • 每一次 new Promise() 构造函数得到的实例对象
    • 都可以通过原型链的方式访问到 .then() 方法,例如 p.then()
  3. .then() 方法用来预先指定成功和失败的回调函数
    • p.then(成功的函数, 失败的函数)
    • p.then(result => {}, error => {})
    • 调用 .then() 方法时,成功的回调函数是必选的,失败的回调函数是可选的

.then() 方法的特性

如果上一个 .then() 方法中返回了一个新的 Promise 实例对象,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的链式调用,就解决了回调地狱的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import thenFs from 'then-fs';

thenFs
.readFile('./files/1.txt', 'utf8') // 返回值是 Promise 的实例对象
.then((r1) => {
console.log(r1);
return thenFs.readFile('.files/2.txt', 'utf8');
})
.then((r2) => {
console.log(r2);
return thenFs.readFile('.files/3.txt', 'utf8');
})
.then((r3) => {
console.log(r3);
});

通过 .catch() 捕获错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import thenFs from 'then-fs';

thenFs
.readFile('./files/11.txt', 'utf8')
.then((r1) => {
console.log(r1);
return thenFs.readFile('./files/2.txt', 'utf8');
})
.then((r2) => {
console.log(r2);
return thenFs.readFile('./files/3.txt', 'utf8');
})
.then((r3) => {
console.log(r3);
})
.catch((err) => {
console.log(err.message);
});

Promise.all() 方法

Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)。

1
2
3
4
5
6
7
import thenFs from 'then-fs';

const promiseArr = [thenFs.readFile('./files/3.txt', 'utf8'), thenFs.readFile('./files/2.txt', 'utf8'), thenFs.readFile('./files/1.txt', 'utf8')];

Promise.all(promiseArr).then((result) => {
console.log(result); // [ '333', '222', '111' ] 随机先后
});

Promise.race() 方法

Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的 .then 操作(赛跑机制)。

1
2
3
4
5
6
7
import thenFs from 'then-fs';

const promiseArr = [thenFs.readFile('./files/3.txt', 'utf8'), thenFs.readFile('./files/2.txt', 'utf8'), thenFs.readFile('./files/1.txt', 'utf8')];

Promise.race(promiseArr).then((result) => {
console.log(result); // 333 随机123
});

基于 Promise 封装方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import fs from 'fs'

function getFile(fpath) {
return new Promise((resolve, reject) => {
fs.readFile(fpath, 'utf8', (err, data) {
if(err) return reject(err)
return resolve(data)
})
})
}

getFile('./files/1.txt')
.then(res => console.log(r1))
.catch(err => console.log(err.message))

async/await

async/await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。在 async/await 出现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。

  • .then 链式调用的优点:解决了回调地狱的问题
  • .then 链式调用的缺点:代码冗余、阅读性差、不易理解

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
import thenFs from 'then-fs';

async function getFile() {
const r1 = await thenFs.readFile('./files/1.txt', 'utf8');
console.log(r1);
const r2 = await thenFs.readFile('./files/2.txt', 'utf8');
console.log(r2);
const r3 = await thenFs.readFile('./files/3.txt', 'utf8');
console.log(r3);
}

getFile();

注意事项

  1. 如果在 function 中使用了 await,则 function 必须被 async 修饰
  2. 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import thenFs from 'then-fs';

console.log('A');
async function getAllFile() {
console.log('B');
const r1 = await thenFs.readFile('./files/1.txt', 'utf8');
const r2 = await thenFs.readFile('./files/2.txt', 'utf8');
const r3 = await thenFs.readFile('./files/3.txt', 'utf8');
console.log(r1, r2, r3);
console.log('D');
}

getAllFile();
console.log('C');

// 最终输出顺序
// A、B、C、111 222 333、D

Event Loop

JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。单线程执行任务队列的问题:如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。

同步任务和异步任务

为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类:

  1. 同步任务(synchronous)
    • 又叫做非耗时任务,指的是在主线程上排队执行的那些任务
    • 只有前一个任务执行完毕,才能执行后一个任务
  2. 异步任务(asynchronous)
    • 又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行
    • 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数

执行过程

JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop(事件循环)。

  1. 同步任务由 JavaScript 主线程次序执行
  2. 异步任务委托给宿主环境执行
  3. 已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
  4. JavaScript 主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行
  5. JavaScript 主线程不断重复上面的第 4 步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import thenFs from 'then-fs';

console.log('A');

thenFs.readFile('./files/1.txt', 'utf8').then((data) => console.log('B'));

setTimeout(() => {
console.log('C');
}, 0);

console.log('D');

// 最终输出顺序
// A、D、C、B

宏任务和微任务

JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:

  1. 宏任务(macrotask)
    • 异步 Ajax 请求、
    • setTimeout、setInterval、
    • 文件操作
    • 其它宏任务
  2. 微任务(microtask)
    • Promise.then、.catch 和 .finally
    • process.nextTick
    • 其它微任务

执行顺序

每个宏任务执行完后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务后,再执行下一个宏任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setTimeout(() => {
console.log(1); // 异步任务 => 宏任务
}, 0);

new Promise((resolve) => {
console.log(2); // 同步任务
resolve();
}).then(() => {
console.log(3); // 异步任务 => 微任务
});

console.log(4); // 同步任务

// 最终输出顺序
// 2、4、3、1

经典面试题

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
console.log(1); // 同步任务

setTimeout(() => {
console.log(2); // 异步任务 => 同步任务
new Promise((resolve) => {
console.log(3); // 异步任务 => 同步任务
resolve();
}).then(() => console.log(4)); // 异步任务 => 微任务
}, 0);

new Promise((resolve) => {
console.log(5); // 同步任务
resolve();
}).then(() => console.log(6)); // 异步任务 => 微任务

setTimeout(() => {
console.log(7); // 异步任务 => 同步任务
new Promise((resolve) => {
console.log(8); // 异步任务 => 同步任务
resolve();
}).then(() => console.log(9)); // 异步任务 => 微任务
}, 0);

// 最终输出顺序
// 1、5、6、2、3、4、7、8、9