ES6模块化

1. 回顾 node.js 中如何实现模块化

node.js 遵循了 CommonJS 的模块化规范。其中:

  • 导入其他模块使用 require () 方法
  • 模块对外共享成员使用 module.exports 对象

模块化的好处:
大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己


2. 前端模块化规范的分类

在 ES6 模块化规范诞生前,有其他如 AMD 、 CMD 、 CommonJS 模块化规范。但这些都不是官方的,还是存在一定的差异性和局限性

例如:
AMD 和 CMD 适用于浏览器端的 JavaScript 模块化
CommonJS 适用于服务器端的 JavaScript 模块化

为了降低学习难度和开发成本,统一的 ES6 模块化 就诞生了!


3. 什么是 ES6 模块化规范

ES6 模块化规范中定义:

  • 每个 JS 文件都是一个独立的模块
  • 要导入模块 使用 import 关键字
  • 向外共享模块 使用 export 关键字

4. ES6模块化的基本语法

主要包含以下三种用法

  • 默认导出和默认导入
  • 按需导出和按需导入
  • 直接导入并执行模块中的代码

4.1 默认导出

语法:export default 默认导出的成员

let n1 = 10
let n2 = 20
function show () {}

// 向外共享对象
export default {
    n1,
    show
}

4.2 默认导入

语法:import 接收名称 from ’ 模块标识符 ’

import m1 from './1. 默认导出'

console.log(m1);

只能拿到 n1 和 show


4.3 按需导出 和 按需导入

按需导出 语法:emport 按需导出的成员

export let s1 = 'aaa'
export let s2 = 'ccc'
export function say() { }

按需导入 语法:import {s1} from ’ 模块标识符 ’

import { s1, s2, say } from "./3.按需导出";

Promise

1. 回调地狱

多层回调函数的相互嵌套,就形成了回调地狱

setTimeout(() => {   // 第一层
    console.log('延时一秒后输出');
    setTimeout(() => {  //第二层
        console.log('再延时两秒输出');
        setTimeout(() => {  //第三层
            console.log('再延时三秒');
        }, 3000)
    }, 2000)
}, 1000);

缺点:代码耦合性强,牵一发而动全身,难以维护

为解决这个问题,提出了 Promise 的概念


1.1 Promise的基本概念

① Promise 是一个构造函数

  • 我们可以创建 Promise 的实例 const p = new Promise()
  • new 出来的 Promise 实例对象,代表一个异步操作

② Promise.prototype 上包含一个 .then() 方法

③ .then() 方法用来预先制定成功和失败的回调函数

  • p.then( 成功的回调函数,失败的回调函数 )
  • p.then( result => { } , error => { } )
  • 成功的回调函数必选,失败的回调函数可选

1.2 then-fs 异步读取文件内容

调用 then-f 提供的 readFile() 方法,可以异步的读取文件的内容,它的返回值是 Promise 的实例对象。

因此可以调用 .then() 方法 为每个 Promise 异步操作制定成功失败之后的回调函数。

import thenFs from 'then-fs'

// thenFs.readFile('./files/1.txt', 'utf8') 会拿到它的 Promise 对象
thenFs.readFile('./files/1.txt', 'utf8').then((r1) => { console.log(r1) })
thenFs.readFile('./files/2.txt', 'utf8').then((r2) => { console.log(r2) })
thenFs.readFile('./files/3.txt', 'utf8').then((r3) => { console.log(r3) })
// 当前我们只是异步的读取数据,无法保证顺序
  • 优化:
import thenFs from 'then-fs'

// thenFs.readFile('./files/1.txt', 'utf8') 会拿到它的 Promise 对象
thenFs.readFile('./files/1.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);
    })

2. 通过 .catch 捕获错误

在 Promise 的链式操作中 如果发生了错误,可以使用 Promise.prototype.catch 方法进行捕获和处理

import thenFs from 'then-fs'

// thenFs.readFile('./files/1.txt', 'utf8') 会拿到它的 Promise 对象
thenFs.readFile('./files/11.txt', 'utf8')  // 没有 11.txt文件
    .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);
    })
    // 报错: ENOENT: No such file

3. Promise.all() 方法

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

import thenFs from "then-fs";

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

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

4. Promise.race() 方法

只要任何一个异步操作完成,就立即执行下一步的 .then 操作(赛跑机制

import thenFs from "then-fs";

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

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

5. 封装读文件的方法

方法封装的要求:

  • ① 方法的名称要定义为 getFile
  • ② 方法接收一个 形参 fpath ,标识要读取的文件的路径
  • ③ 方法的返回值为 Promise 实例对象

5.1 getFile 方法的基本定义

import fs from 'fs'

function getFile(fpath) {
    // 如果调用 getFile 函数,就返回一个 Promise 对象
    return new Promise(function (resolve, reject) {
        fs.readFile(fpath, 'utf8', (err, dataStr) => {
            // 如果发生错误,就把 err 传进去 reject对象
            if (err) return reject(err)
            // 如果上一句没有执行 说明成功了 就把 dataStr 传给 resolve
            resolve(dataStr)
        })
    })
}

getFile('./files/1.txt').then((r1) => { console.log(r1) }, (err) => { console.log(err.message); })

async / await

1. 什么是 async / await

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

2. async / await 基本使用

import thenFs from 'then-fs'

async function getAllFile() {
    const r1 = await thenFs.readFile('../files/1.txt', 'utf8');
    console.log(r1);
    // 111
}

getAllFile();

3. async / await 注意事项

在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行


// 这里 A 是同步执行
console.log('A');
async function getAllFile() {
    console.log('B');  // 直到这里都是 同步执行,下面碰到第一个 await,于是退出主线程并进入第 12 行代码,后面的都是异步执行
    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');

宏任务和微任务

1. 什么是宏任务和微任务

JavaScript 把异步任务分为了两类,分别是:
① 宏任务

  • 异步 AJAX 请求
  • setTimeout、setInterval
  • 文件操作
  • 其他宏任务

② 微任务

  • promise.then, .catch 和 .finally
  • process.nextTick
  • 其他微任务

2. 宏任务和微任务的执行顺序

宏任务完成 -> 判断有微任务? -> 执行所有微任务 -> 下一个宏任务

API接口案例

1. 案例需求

基于 MySQL 数据库 + Express 对外提供用户列表的API接口服务。用到技术如下:

  • 第三方包 express 和mysql2
  • ES6 模块化
  • Promise
  • async / await

2. 主要实现步骤

① 搭建项目的基本结构
② 创建基本的服务器
③ 创建 db 数据库操作模块
④ 创建 user_ctrl 业务模块
⑤ 创建 user_router 路由模块

3. 搭建项目的基本结构

① 启用 ES6 模块化支持

  • 在 package.json 中声明 “type”:“module”

② 安装第三方依赖包

  • 运行 npm install express@4.17.1 mysql2@2.2.5

4. 创建基本的服务器

import express from 'express'
const app = express()

// 启用服务器,并启动在 80 端口
app.listen(80, () => {
    console.log('server running at http://127.0.0.1');
})

5. 创建 db 数据库操作模块

新创建文件夹 db

import mysql from 'mysql2'

// mysql.creatPool() 的返回值 是一个数据库的连接对象
const pool = mysql.createPool({
    host: '127.0.0.1',     // 指定操作哪台电脑上的数据库
    port: 3306,            // 指定要连接的数据库的端口号
    database: 'my_db_01',  // 指定操作哪个数据库
    user: 'root',          // 登录数据库的账号
    password: 'admin123'   // 密码
});

// 将 pool 导出,供外界使用
export default pool.promise();

6. 创建 user_ctrl 模块

import db from '../db/index.js'

// 使用 ES6 按需导出语法
export async function getAllUser(req, res) {
    const [rows] = await db.query('select id, username, nickname from ev_users')
    res.send({
        status: 0,
        message: '获取用户列表数据成功!',
        data: rows,
    })
}
getAllUser()

7. 创建 user_router 模块

import express from 'express'
import { getAllUser } from '../2.controller/user_ctrl.js'

// 1. 创建路由对象
const router = new express.Router();

// 3. 挂载一个路由规则
// router.get 监听客户端的 GET 请求
// 只要用户是 GET 请求,并且请求 /user 这个地址,就把这次请求,交给 getAlluser 这个函数处理
router.get('/user', getAllUser)

// 2. 将路由对象共享出去
export default router


// 4. 接下来,回到 app.js 文件中挂载

app.js:


import express from 'express'
import userRouter from './3.router/user_router.js'
const app = express()

// 增:1. 调用 app.use('') 进行挂载
// 如果用户是以 /api 方式进行访问,就指定路由模块 userRouter
app.use('/api', userRouter)

// 启用服务器,并启动在 80 端口
app.listen(80, () => {
    console.log('server running at http://127.0.0.1');
})

目标

  • 能够知道如何使用 ES6 的模块化语法
  • 能够知道如何使用 Promise 解决回调地狱的问题
  • 能够知道如何使用 async / await 简化 Promise 的调用
  • 能够说出什么是 EventLoop
  • 能够说出宏任务和微任务的执行顺序
Logo

前往低代码交流专区

更多推荐