Appearance
内容概述
这一讲介绍下ES6新增的几个常用的特性,重点是Promise和async/await
教学目的
- 掌握模板字符串、解构赋值、扩展操作符、对象增强等语法
- 了解ES6类的语法
- 掌握Promise对象用法和Promise常用方法
- 掌握async/await语法
具体内容
- 模板字符串
- 解构赋值
- 扩充操作符的使用场景
- 对象增强语法
- ES6新增类和继承语法
- Promise对象和常用方法(then、catch、finally、Promise.all和Promise.race)
- async/await
ES6
ES6是一个泛称,它泛指ECMAScript 2015(ES2015)及后续的所有版本。ES6的引入带来了许多新特性,这些特性增强了JavaScript的语言功能,使得开发者能够编写更高效、更安全的代码。
模板字符串
JavaScript 中的模板字符串是一种允许你嵌入表达式的字符串字面量。模板字符串使用反引号``而非单引号''或双引号""来定义,并且可以包含占位符 ${...},其中可以放入任意的 JavaScript 表达式,这些表达式会在字符串被创建时求值并转换为字符串。
模板字符串为字符串的拼接和格式化提供了更加清晰和简洁的语法。
基本用法
javascript
let name = "javascript";
console.log(`hello ${name}`);表达式
你可以在 ${...} 中放入任何有效的 JavaScript 表达式,包括函数调用、算术运算等。
javascript
const sum = (a, b) => a + b
console.log(`1+1=${1+1}`)
console.log(`sum(1,1)=${sum(1,1)}`)多行字符串
模板字符串可以包含多行文本,而不需要使用特殊的转义序列(如 \n)。
javascript
let multiLineString = `This is a string that spans across
multiple lines.`;
console.log(multiLineString);
// 输出:
// This is a string that spans across
// multiple lines.解构赋值
JavaScript 中的解构赋值是一种表达式,它允许你从数组或对象中提取数据,并将其赋值给声明的变量。这是一种非常便捷的方式来处理复杂的数据结构,使代码更加简洁和易于理解。
数组解构
在数组解构中,你可以从数组中提取值,并将它们赋给声明的变量。
javascript
let [a, b, c] = [1, 2, 3];
console.log(a); // 输出: 1
console.log(b); // 输出: 2
console.log(c); // 输出: 3
// 你可以跳过某些值
let [first, , third] = [1, 2, 3];
console.log(first); // 输出: 1
console.log(third); // 输出: 3
// 默认值
let [x, y = 'default'] = [1];
console.log(x); // 输出: 1
console.log(y); // 输出: 'default'
// 嵌套数组解构
let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo); // 输出: 1
console.log(bar); // 输出: 2
console.log(baz); // 输出: 3对象解构
在对象解构中,你可以从对象中提取属性,并将它们赋给声明的变量。
javascript
let { foo, bar } = { foo: 'hello', bar: 'world' };
console.log(foo); // 输出: 'hello'
console.log(bar); // 输出: 'world'
// 你可以为变量指定不同的名称
let { foo: baz } = { foo: 'hello' };
console.log(baz); // 输出: 'hello'
// 默认值
let { baz = 'default' } = {};
console.log(baz); // 输出: 'default'
// 嵌套对象解构
let { user: { name, age } } = { user: { name: 'John', age: 30 } };
console.log(name); // 输出: 'John'
console.log(age); // 输出: 30函数参数解构
解构赋值也可以用于函数参数,使得从函数参数中提取数据变得更加方便。
javascript
function greet({ name, age }) {
console.log(name);
console.log(age);
}
greet({ name: 'abc', age: 18 });扩展操作符
数组的合并
javascript
let arr1 = [1, 2, 3];let arr2 = [4, 5, 6];let combined = [...arr1, ...arr2]; // 结果是 [1, 2, 3, 4, 5, 6]克隆数组
javascript
let arr = [1, 2, 3];let clone = [...arr]; // 结果是 [1, 2, 3]在函数调用中使用扩展操作符
javascript
function myFunction(...args) { console.log(args); // 输出所有参数的数组}myFunction(1, 2, 3); // 结果是 [1, 2, 3]在构造函数中使用扩展操作符
let parts = ['shoulders', 'knees'];let lyrics = ['head', ...parts, 'and', 'toes'];// 结果是 ['head', 'shoulders', 'knees', 'and', 'toes']在对象中使用扩展操作符
javascript
let obj1 = { foo: 1, bar: 2 };let obj2 = { ...obj1, baz: 3 };// 结果是 { foo: 1, bar: 2, baz: 3 }对象增强
简化对象初始化
在ES6之前的语法中,初始化对象的时候,如果刚好对象的属性名和变量名称一致,我们仍然需要如同下面的方式来赋予对象属性值。
javascript
const name = 'javascript'
const age = 18
const obj = {
name: name,
age: age
}
console.log(obj);在ES6中则进行了简化,通过变量直接进行对象初始化。代码如下:
javascript
const name = 'javascript'
const age = 18
const obj = {
name,
age
}
console.log(obj)简化定义对象函数
在ES6之前的语法中,给对象定义方法需要类似于如下的代码
javascript
const obj = {
fn: function () {
console.log(123);
}
}而使用ES6语法的话,可以简化为:
javascript
const obj = {
fn() {
console.log(123);
}
}动态对象键
有些时候,我们所创建的对象,它的属性名是动态的,比如属性名是由一个变量来确定的。如下面的代码:
javascript
const key = 'abc'
const obj = {
[key]: 123
}
console.log(obj)类和继承
类的基本语法
使用 class 关键字可以声明一个类。类可以包含构造函数和类方法。
javascript
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 类方法
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
// 创建类的实例
const p = new Person('abc', 18);
console.log(p.name); // 访问实例属性
p.sayHello(); // 调用实例方法构造函数和实例属性
构造函数通过 constructor 方法定义,实例属性直接在构造函数中用 this 关键字声明。
类方法
类方法是在类的原型上定义的,可以通过实例调用。
静态方法
使用 static 关键字可以定义类的静态方法,静态方法是类的方法而不是实例的方法,可以直接通过类名调用。
javascript
class MathUtils {
static add(x, y) {
return x + y;
}
static subtract(x, y) {
return x - y;
}
}
console.log(MathUtils.add(5, 3)); // 输出: 8
console.log(MathUtils.subtract(5, 3)); // 输出: 2继承
使用 extends 关键字可以实现类的继承。子类可以通过 super 关键字调用父类的构造函数和方法。
javascript
class Person {
// 构造函数
constructor(name) {
this.name = name;
}
// 类方法
sayHello() {
console.log(`My name is ${this.name}`);
}
}
class Student extends Person {
// 子类构造函数
constructor(name, score) {
// 调用父类构造函数
super(name);
this.score = score;
}
// 子类方法
study() {
console.log('studying');
}
// 覆盖父类方法
sayHello() {
console.log(`My name is ${this.name} and score is ${this.score}`);
}
}
const dog = new Student('jack', 100);
dog.study();
dog.sayHello();Promise
JavaScript 中的 Promise 是一种用于异步计算的对象。一个 Promise 代表了一个最终可能完成(fulfilled),也可能失败(rejected)的异步操作及其结果值。使用 Promise 可以让你以更加优雅和易于理解的方式处理异步操作,避免了传统的回调地狱(Callback Hell)问题。
先看一个案例,从网络上加载3个资源, 要求加载完资源1才能加载资源2, 加载完资源2才能加载资源3
javascript
function request(fn) {
setTimeout(function () {
fn("拿到的数据");
}, 1000);
}
request(function (data) {
console.log(data, 1);
request(function (data) {
console.log(data, 2);
request(function (data) {
console.log(data, 3);
});
});
});为了保存异步代码的执行顺序, 那么就会出现回调函数层层嵌套,如果回调函数嵌套的层数太多, 就会导致代码的阅读性, 可维护性大大降低,而Promise对象可以将异步操作以同步流程来表示, 避免了回调函数层层嵌套(回调地狱)
基本用法
一个 Promise 有三种状态:
- Pending(等待中):初始状态,既不是成功,也不是失败状态。
- Fulfilled(已成功):意味着操作成功完成。
- Rejected(已失败):意味着操作失败。
- 状态一旦改变既不可逆, 从Pending变为Fulfilled, 那么永远都是Fulfilled;从Pending变为Rejected, 那么永远都是Rejected
创建 Promise
你可以使用 new Promise(executor) 构造函数来创建一个新的 Promise 实例。executor 是一个执行器函数,它接受两个函数作为参数:
resolve:当异步操作成功完成时调用,并将Promise的状态从 "pending" 变为 "fulfilled",同时将操作的结果作为参数传递出去。reject:当异步操作失败时调用,并将Promise的状态从 "pending" 变为 "rejected",同时将错误作为参数传递出去。
javascript
let promise = new Promise(function(resolve, reject) {
// 异步操作
setTimeout(() => {
// 假设操作成功
resolve('操作成功');
// 如果操作失败,则调用 reject('操作失败');
}, 1000);
});使用 Promise
你可以使用 .then() 方法来处理 Promise 成功的情况,使用 .catch() 方法来处理失败的情况。.then() 方法接受两个可选的回调函数作为参数,第一个用于处理成功的情况,第二个(可选)用于处理错误。.catch() 方法只处理错误情况。
javascript
promise.then(
result => {
console.log(result)
}, // 处理成功的情况
error => {
console.log(error)
} // 可选,但通常使用 .catch() 处理错误
).catch(
error => {
console.log(error)
} // 处理错误
);
// 更常见的用法是只使用 .then() 的第一个参数和 .catch()
promise.then(result => {
})
.catch(error => {
console.log(error)
});then
- then方法接收两个参数,第一个参数是状态切换为成功时的回调,第二个参数是状态切换为失败时的回调
javascript
let promise = new Promise(function (resolve, reject) {
// resolve(); // 将状态修改为成功
reject(); // 将状态修改为失败
});
promise.then(function () {
console.log("成功");
}, function () {
console.log("失败");
});- 在修改promise状态时, 可以传递参数给then方法中的回调函数
javascript
let promise = new Promise(function (resolve, reject) {
// resolve("111"); // 将状态修改为成功 success("111");
reject("aaa"); // 将状态修改为失败 error("aaa");
});
promise.then(function (data) {
console.log("成功", data);
}, function (data) {
console.log("失败", data);
});- 同一个promise对象可以多次调用then方法,当该promise对象的状态时所有then方法都会被执行
javascript
let promise = new Promise(function (resolve, reject) {
// resolve(); // 将状态修改为成功
reject(); // 将状态修改为失败
});
promise.then(function () {
console.log("成功1");
}, function () {
console.log("失败1");
});
promise.then(function () {
console.log("成功2");
}, function () {
console.log("失败2");
});- then方法每次执行完毕后会返回一个新的promise对象
javascript
let promise = new Promise(function (resolve, reject) {
resolve(); // 将状态修改为成功
// reject(); // 将状态修改为失败
});
let p2 = promise.then(function () {
console.log("成功1");
}, function () {
console.log("失败1");
});
console.log(p2);
console.log(promise === p2);- 可以通过上一个promise对象的then方法给下一个promise对象的then方法传递参数,无论是在上一个promise对象成功的回调还是失败的回调传递的参数,都会传递给下一个promise对象成功的回调
javascript
let promise = new Promise(function (resolve, reject) {
// resolve("111"); // 将状态修改为成功
reject("aaa"); // 将状态修改为失败
});
let p2 = promise.then(function (data) {
console.log("成功1", data);
return "222";
}, function (data) {
console.log("失败1", data);
return "bbb";
});
p2.then(function (data) {
console.log("成功2", data);
}, function (data) {
console.log("失败2", data);
});- 如果then方法返回的是一个Promise对象, 那么会将返回的Promise对象的执行结果中的值传递给下一个then方法
javascript
let promise1 = new Promise(function (resolve, reject) {
resolve("111"); // 将状态修改为成功
// reject("aaa"); // 将状态修改为失败
});
let promise2 = new Promise(function (resolve, reject) {
// resolve("222"); // 将状态修改为成功
reject("bbb"); // 将状态修改为失败
});
let p = promise1.then(function (data) {
console.log("成功1", data);
return promise2;
}, function (data) {
console.log("失败1", data);
return "bbb";
});
p.then(function (data) {
console.log("成功2", data);
}, function (data) {
console.log("失败2", data);
});catch
它的特点和then第二个参数回调一样,有一点特殊的是catch方法可以捕获上一个promise对象then方法中的异常
javascript
let promise = new Promise(function (resolve, reject) {
resolve();
});
// promise.then(function () {
// console.log("成功");
// console.log(xxx);
// }, function () {
// console.log("失败");
// });
promise.then(function () {
console.log("成功");
console.log(xxx);
}).catch(function (e) {
console.log("失败", e);
});finally
它允许你指定不管 Promise 最终是被 fulfilled(成功)还是被 rejected(失败),都会执行的操作。
javascript
let promise = new Promise(function (resolve, reject) {
// resolve(); // 将状态修改为成功
reject(); // 将状态修改为失败
});
promise.finally(function () {
console.log("无论成功和失败都执行");
});- finally() 方法总是会被调用,无论 Promise 的状态是 fulfilled 还是 rejected。
- finally() 方法不接受任何参数,也不关心 Promise 的结果。
- finally() 方法返回一个新的 Promise。这意味着你可以在 finally() 之后链式调用 then() 或 catch()。但请记住,finally() 中的代码不会改变之前 Promise 的结果。
Promise.all
Promise.all 方法用于等待多个 Promise 对象全部完成,然后返回一个包含所有结果的数组,如果其中任何一个 Promise 失败,则整个操作失败。
javascript
const promise1 = new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000)
})
const promise2 = new Promise(resolve => {
setTimeout(() => {
resolve()
}, 2000)
})
const promise3 = new Promise(resolve => {
setTimeout(() => {
resolve()
}, 3000)
})
const promises = [promise1, promise2, promise3];
Promise.all(promises)
.then(results => {
// 处理所有成功的结果
})
.catch(error => {
// 处理失败的情况
});Promise.race
Promise.race 方法用于等待多个 Promise 对象中的任何一个完成,然后返回第一个完成的结果,无论是成功还是失败。
javascript
const promise1 = new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000)
})
const promise2 = new Promise(resolve => {
setTimeout(() => {
resolve()
}, 2000)
})
const promise3 = new Promise(resolve => {
setTimeout(() => {
resolve()
}, 3000)
})
const promises = [promise1, promise2, promise3];
Promise.race(promises)
.then(results => {
// 处理所有成功的结果
})
.catch(error => {
// 处理失败的情况
});创建已解决或已拒绝的 Promise
有时候,我们希望立即创建一个已解决(fulfilled)或已拒绝(rejected)的 Promise 对象,可以使用 Promise.resolve 和 Promise.reject。
javascript
const resolvedPromise = Promise.resolve('Resolved value');
const rejectedPromise = Promise.reject('Rejected reason');async/await
async/await 是 JavaScript 中用于处理异步操作的一种非常强大的语法结构,它使得异步代码看起来和写起来更像是同步代码,极大地提高了代码的可读性和可维护性。async 关键字用于声明一个异步函数,而 await 关键字用于等待一个异步操作的完成。
async 函数
当一个函数被声明为 async 时,它会隐式地返回一个 Promise。如果函数正常结束(即没有显式地返回一个 Promise),则 JavaScript 会自动将函数的返回值包装在一个已解决的 Promise 中。如果函数执行过程中抛出了异常,则返回的 Promise 会被拒绝,并包含抛出的错误值。
javascript
async function fetchData() {
return 'some data';
}
fetchData().then(data => console.log(data)); // 输出: some dataawait 表达式
await 关键字只能在 async 函数内部使用。await 会暂停 async 函数的执行,等待 Promise 解决(resolve)或拒绝(reject),然后继续执行 async 函数并返回解决的值。如果 Promise 被拒绝,await 表达式会抛出一个错误,这个错误可以用 try...catch 语句捕获。
javascript
async function fetchAndLog() {
try {
const data = await fetchData(); // 假设 fetchData 是一个返回 Promise 的函数
console.log(data);
} catch (error) {
console.error('Fetch failed:', error);
}
}
fetchAndLog(); // 假设 fetchData 解决了,则输出: some dataawait只能在async函数内部使用。await会暂停当前async函数的执行,但不会阻塞整个 JavaScript 运行时或浏览器中的其他代码执行。await可以用于任何返回Promise的函数或表达式。- 如果
await后面跟的不是一个Promise,那么它会被隐式地转换为一个已解决的Promise,其值为该非Promise表达式的值。
作业
- 编写一个异步函数sleep实现定时效果,接收一个参数表示需要定时的时间,单位ms
javascript
function sleep (time) {
}
sleep(3000).then(() => {
console.log('3s后执行该行代码');
})- 在题目1实现sleep函数的基础上,写一个函数,入参是3个字符串,函数内部实现1s之后打印第1个字符串,再过2s之后打印第2个字符串,再过3s之后打印第3个字符串,写出两种方式(Promise链式编程和async/await语法形式)