1. 1. try…catch
  2. 2. async/await
  3. 3. 扩展运算符

try…catch

  • try...catch 用来检测代码执行过程中出现的错误,并捕获错误(可以避免由于某个js错误造成页面渲染出错)
  • 实用程度:
1
2
3
4
5
try{
// 这里是我们正常的业务逻辑,改写什么就写什么
} catch(error) {
// 如果try模块内的代码执行出错,出错之后的代码不会再执行,此时会进入catch模块,error是具体的出错原因,在这里可以对错误进行处理
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
const data = null;
const list = data.map(item => {
return {name: item.name, id: item.id};
})
} catch(error) {
console.log('捕获错误', error);
}
// 正常情况下我们会很少见到这种写法,data是个null,结果还去遍历null,正常人都不会这么写

// 但是很多时候,这个data是后端接口给我们返回来的,正常data应该是个数组,但是后端由于没有查询到数据,该返回空数组的时候,后端返回了null,就造成了我们代码出错

// 如果我们不做处理,这个list还是要渲染到页面上的那么就会造成完全空白

// 此时就可以加个try...catch用来做个兼容的处理

async/await

  • async/await 是ES8写入的JS标准

  • 我们使用async 来定义一个异步函数,使用await 等待一个异步的执行结果(准确说await 等待一个Promise对象。await 必须在async function 内部使用),async隐式的返回一个Promise;(官方描述:async function 用来定义一个返回 AsyncFunction对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise返回其结果。如果你在代码中使用了异步函数,就会发现它的语法和结构会更像是标准的同步函数。)

  • 实用程度:

  • 重要程度:

  • async/await 可以让我们以更加优雅的姿势来写异步函数,或者说采用async/await之后,我们就不需要再去考虑这个函数是不是异步的。在此之前,对于异步函数,我们必须等待异步函数执行完成之后,拿到异步函数的结果,才可以继续往下进行,基于此必须写深深的回调,在回调里拿结果,在回调里继续写异步…….俗称“回调地狱”。ES6的出现,带来了新的异步解决方案Promise, 但Promise 真的是完美的吗?虽然我们确实走出了“回调地狱”,但相对的不过是把回调变成了纵向的,形成了一个纵向的回调链。异步编程的最高境界,就是应该让我们不用再去关心它是不是异步。async/await的出现,就是让我们以同步的方式来编写异步代码。

  • async/await 依赖于Promise,是Generator 函数的语法糖。async/await 不会替换Promise,但是好像可以替换Generator,所以,Generator就不讲了(我自己也不怎么用)

1
2
3
// 使用 async 定义一个异步函数
const fn = async () => {} // 箭头函数
async function fn2() {} // function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 本来不想用延时器的,但实在没想到更适合的例子

const fn = () => {
let time = '18:35:36'
setTimeout(() => {
time = '18:35:41';
console.log(time);
return time;
}, 5000);
}
const fn2 = () => {
const time = fn();
console.log('fn执行结果', time)
}
fn2();

如上代码,我们本意是想fn() 执行结束之后拿到返回值,将返回值给变量time 然后打印出time,但由于fn()内的setTimeout是异步执行的,并不会等待 5秒等fn执行完成之后再打印time,所以造成的结果就是先打印time = undefined,等了5秒后fn才执行完。

但是这是我们理想的代码写法,虽然目前它没有按我们的理想执行,但使用async/await 之后,我们就可以使我们理想的代码按照我们的理想去执行。

结合Promise去写理想代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fn = () => {
let time = '18:35:36';
return new Promise((resolve,reject) => {
setTimeout(() => {
time = '18:35:41';
console.log(time);
resolve(time)
}, 5000);
})
}
const fn2 = async () => {
const time = await fn(); // await等待Promise结果/加了await之后,js执行到此暂停,直到这个函数返回Promise或者出错
console.log('fn执行结果', time)
}
fn2();

最佳实践应该是try...catch + async/await + Promise,三者结合写段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const createPromise = () => {
return new Promise((reslove,reject) => {
// 在这里去写异步操作,完成之后,修改promise状态。这里随便写写
const num = parseInt(Math.random()*2); // 取个0或者1的随机数
if(num) {
reslove({'success': num}); // 取的随机数是0或者1,1 的时候是true,调用resolve()将promise修改为成功状态
} else {
reject({'fail': num}); // 取的随机数是0或者1,0 的时候是false,调用reject()将promise修改为失败状态
}
});
}
const fn = async () => {
try {
const resList = await Promise.all([createPromise(),createPromise(),createPromise()]);
console.log('成功', resList); // promise成功状态继续往下走,返回失败状态,就进catch阶段
} catch(error) {
console.log('失败', error); // promise成功状态继续往下走,返回失败状态,就进catch阶段
// 其实一直没有说,如果你不需要对错误进行处理,catch中可以什么都不写
// 或者如果你不想在这里对错误进行处理,可以使用throw 将错误抛出去
// 如果不想处理错误,也不想接收参数不想接收,catch后可以直接跟大括号,但是这个是ES10提出的,尚未写入标准,兼容性也存疑
}
}

扩展运算符

... ,用来将数组或者对象进行展开,也可以展开字符串,还可以在函数调用时对入参进行展开(描述不准确,看代码演示);(官方描述: 展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。)

  • 常用来用来实现数组和对象的深拷贝
1
2
3
4
5
6
7
8
9
10
/************************** 浅拷贝 ****************************/
let numList1 = [1,2,3,4,5,6,7,8];
let numList2 = numList1;
// 这里给forEach传了第二个参数,用来充当this
// 这里使用了function定义函数,因为使用箭头函数第二个参数会被忽略
numList2.forEach(function(item,index) {
this[index] = item * 3;
},numList2);
console.log(numList1, numList2);

我们本意是希望将numList1拷贝一份给numList2,然后对numList2进行修改,numList1我们可能在其他地方还需要用,但由于js对引用类型实行的时浅拷贝,所以会造成numList1也被改变,基于此,我们希望numList2改变不会影响numList1,我们就需要对numList1进行深拷贝

1
2
3
4
5
6
7
8
/************************** map深拷贝 ****************************/
const numList1 = [1,2,3,4,5,6,7,8];
const numList2 = [...numList1]
numList2.forEach(function(item,index) {
this[index] = item * 3;
},numList2);
console.log(numList1, numList2);
// 实现这功能最适合的应该是map()方法,一步到位。这里是为了刻意演示扩展运算符
  • 对象深拷贝
1
2
3
4
5
const obj1 = {name: '李白'};
const obj2 = {age: 18};
const obj3 = {job: '诗人'};
const obj4 = {...obj1,...obj2,...obj3,friend: '汪伦'};
console.log(obj4);

对于以上写法,如果obj2中的属性在obj1中已经存在了,则obj2中的属性会覆盖obj1 中的属性;后覆盖前

以上写法也是刻意为之,对象深拷贝还有其他API

  • 字符串扩展运算符和在函数调用时
1
2
3
4
5
6
console.log(...'qasxxxxxdrrrrrrrfgbbbhuuuujmmmmko');

const fn = (x,y,z) => {
return `${x}----${y}----${z}`;
}
fn(...['who','are','you']);