Node.js - 自我总结

1. 什么是 Node.js

Node.js:是一个基于Chrome V8 引擎的 JavaScript运行环境

Node.js官方地址: http://nodejs.org/zh-cn

注意:

  1. 浏览器是JavaScript的前端运行环境。
  2. Node.jsJavaScript的后端运行环境。
  3. Node.js中无法调用DOMBOM等浏览器内置API

2. 使用node命令执行JS文件

操作步骤:

  1. 打开终端
  2. 注意路径,进入到你要运行的项目文件目录下
  3. 输入node 要执行的js文件

注意:

  1. 执行文件的时候,需要保证node xxx.js这种格式
  2. node只能运行JS代码(也就是不要node xxx.html)

3. 什么是模块化

模块化就是,把一个大文件拆分成若干个小文件,而且还能把小文件通过特定的语法组合到一起实现过程。

4. 模块化规范

  • AMD - 依赖前置 (require.js)
  • CMD - 依赖就近 (sea.js)
  • CommonJS(Node中的模块化,使用的是这种方案)
  • ES6(ES6-JS的语法)

Node使用的是CommonJS规范

5. 模块的分类

  • 自定义模块
    • NodeJS中,创建的JS文件都是自定义模块。(也就是处处皆模块)
  • 内置模块(核心模块)
    • 安装Node之后,自带了很多内置模块。我们可以直接加载使用他们。
  • 第三方模块
    • 其他人编写的模块,发布到 npm 网站 上,我们可以下载使用。

6. 自定义模块

  1. 导出文件 - 导出

    方式一:

    module.exports = { 变量名, 方法名,…}

    方式二:

    exports.xx = xx

  2. 导入文件 - 导入

    引入:let xxx = require(‘地址URL’)

    console.log(xxx.变量名)

示例:

  1. 导出内容 (m1.js)
const PI = 3.141592645656475634534
function fn() {
  console.log('我是一个方法');
}

// 1. 导出模块 - module.exports 的值是什么外部引入的就是什么
// 第一种方式倾向于导出一个整体
module.exports = {
  PI,
  fn
}

// exports是modules.exports(对象)引用
// 第二种倾向于导出多个内容
// exports.PI = PI
// exports.fn = fn
  1. 导入内容 (userModule.js)
// 引入模块
let m1 = require('./02-m1')
console.log(m1.PI);
console.log(m1.fn);

7. 内置模块

内置模块是Node.js平台自带的一套基本API(功能模块)。也叫做核心模块

注意:

  • 加载内置模块时,不能写路径,这是和加载自定义模块的区别

7.1 path模块

  • path是Node本身提供的API,专门用来处理路径
  • http://nodejs.cn/api/path.html

示例

// 第一步:先加载要使用的模块
const path = require('path')  // 或 let path = require('path')

// 方法1:extname - 获取文件后缀
console.log(path.extname('index.html'))  // .html
console.log(path.extname('readme.md'))  // .md

// 方法2:join - 智能拼接路径
console.log(path.join('./', 'aa', 'cc', '../bb', 'c'))  // aa\bb\c
console.log(path.join('./', 'html', 'index.html'))  // html\index.html

// __dirname - 表示当前js文件的绝对路径
console.log(path.join(__dirname, 'css', 'index.css'))  // F:\黑马程序员\前端就业班\07-node.js\day01\code\lianXi\css\index.css

7.2 fs模块

  • fs,即 file system,文件系统,该模块可以实现对文件、文件夹的操作
  • http://nodejs.cn/api/fs.html

示例

// 第一步:引入 fs 模块
const fs = require('fs')

// 方法1:readFile() - 读取文件
// 第一个形参:必填 - 读取文件的路径
// 第二个形参:可选 - 编码的格式
// 第三个形参:必填 - 回调,文件读取成功要做的那些事
fs.readFile('./test', 'utf-8', function (err,data) {
    // 该回调函数里有两个形参
    // 第一个形参:错误对象,当读取错误时会出现的对象,读取正确的时候为 null
    // 第二个形参:读取文件的结果

    // 如果读取错误返回一条错误信息  并结束
    if (err !== null) return  console.log('读取出错啦~')
    // 执行完毕没错的时候执行
    console.log(data)
})

// 方法2:writeFile() - 写入文件  注:会覆盖之前文件的内容
// 第一个形参:必填 - 写入文件的路径,没有会进行创建
// 第二个形参:必填 - 要写入的数据,可以放对象等等
// 第三个形参:可选 - 编码的格式,不写默认utf-8
// 第四个形参:可选 - 写入完成后的回调函数
fs.writeFile('./test2.txt', '这里是要写入的内容', function (err) {
    if (err !== null) return console.log('写入出错了!!!')
    console.log('写入成功~')
})

7.3 http模块

  • 目标:实现网站服务器
// 引入 http 模块
const http = require("http")
//引入 fs 模块
const fs = require("fs")

// 2. 创建服务器对象
const server = http.createServer()

// 3. 监听客户端的请求
server.on("request", function (request, response) {
  // 当客户端请求,就会触发这个回调函数
  // request请求对象,包含客户端请求时携带的一些信息(参数,路径,请求头。。。)
  // 指定就是端口号后面的路径 - 获取get
  console.log(request.url) // /favicon.ico
  // 获取 请求方式
  console.log(request.method) // GET
  // response相应对象,返回数据时使用
  console.log("有人访问了我们的服务器")
  //相应给客户端
  //相应数据
  // response.end('{ "name" : "zs", "age" : 18}')
  // 相应文件的写法
  // 解决中文乱码
  response.setHeader("Content-Type", "text/html;charset=utf-8")
  fs.readFile("./index.html", "utf8", function (err, data) {
    if (err !== null) {
      return response.end("404")
    }
    response.end(data)
  })
})

// 4. 启动服务器
// 第一个参数:端口号,必填
// 第二个参数:运行成功回调,选填
server.listen(80, function () {
  console.log("服务器启动成功,运行在 http://127.0.0.1:80")
})
// 终止服务器运行 Ctrl+c

8. NPM

8.1 初始化操作

npm init

npm init -y

npm下载 淘宝源

npm config set registry https://registry.npm.taobao.org

  • 示例
# 初始化,需要输入项目信息(可以使用默认,但是项目名称不能为中文不然会报错),输入完成后,可以一路回车
npm init

# 初始化,直接全部采用默认信息
npm init -y
  • 注意:
    1. 初始化之后,会在项目目录中生成 package.json 的文件。
    2. package name 默认使用当前文件夹 当做 包的名字
    3. package name 不能有中文、不能有特殊符号、不能和需要安装的第三方模块同名
  • 建议:
    • 初始化完毕后的操作 - 这样下载国外网址的包会更快

建议在安装第三方模块之前,先执行如下命令。
下面的命令只需要执行一次即可(不管以后重启vscode还是重启电脑,都不需要执行第二次)
npm config set registry https://registry.npm.taobao.org


### 8.2 下载包

> npm install 包名
>
> npm install 模块名 模块名 模块名
>
> npm install -D 包名
>
> npm install -g 包名
>
> npm ls -g --depth 0
>
> npm install

- 下载安装第三方模块

```shell
# 正常的下载安装
npm inster 包名

# 下载指定版本依赖
npm i 包名@版本号

# 下载多个包
npm install 包名 包名 包名...

# 下载开发依赖
npm install -D 包名
# 或
npm install --save-d 包名

# 下载全局 - 下载工具性质的包,比如vue-cli(创建vue项目的脚手架工具)、nrm(切换下载源)
npm install -g nrm
# mac电脑(需要管理员权限)  sudo npm install -g nrm

# 查看全局下载的包
npm ls -g --depth 0

# 根据package.json文件dependencies和devDependencies字段自动下载对应的包,在新接触一个项目后,第一件事就是使用该命令来安装依赖
npm install

# !!!所有的 install 都可以替换为 i
  • 注意:

    • 代码文件夹不能有中文;代码文件夹不能和模块名同名。
  • 关于本地模块的说明

    • 下载安装的模块,存放在当前文件夹的 node_modules 文件夹中,同时还会生成一个记录下载的文件 package-lock.json
    • 下载的模块,在哪里可以使用
      • 在当前文件夹
      • 在当前文件夹的子文件夹
      • 在当前文件夹的子文件夹的子文件夹
      • 反过来讲,当查找一个模块的时候,会在当前文件夹的 node_modules 文件夹查找,如果找不到,则去上层文件夹的node_modules文件夹中查找,…依次类推。

8.3 删除包

npm uninstall 包名

npm unin

# 删除指定第三方包
npm uninstall 包名

# 简化写法
npm un 包名

# 删除多个
npm un 包名 包名...

# 删除全局包
npm un -g 包名

8.4 使用第三方包

// 1. 任何包第一步都需要引入,require里面直接写包名
const pkg = require('包名')
// 2. 参考文档使用包
// 2.1 去官方找,比较优秀的包都是官网的,例如:vue,react,angular,lodash...
// 2.2 npmjs.com 搜索包,包主页会有文档
// 2.3 百度搜

8.5 nrm的使用

**作用:**用来切换下载镜像源

nrm ls

nrm user 源名

# 以列表的形式查看源
nrm ls
# 切换源
nrm use 源名

8.6 package.json 文件

{
  "name": "npm-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "ipconfig"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "jquery": "^3.6.0",
    "moment": "^2.29.3",
    "sass": "^1.51.0"
  }
}
  1. name - 项目名,不能有中文

  2. version - 版本号,只能数字和点

  3. description - 描述

  4. main - 项目的入口文件是哪一个文件

  5. scripts - 声明简化命令

{
  "简化命令": "真实命令(命令行能用的名)",
  "start": "node ./index.js",
  // "start": "ipconfig"
}
// 执行简化命令,在命令行中输入 npm run 简化命令
// start特殊,start可以 npm run start 也可以 npm start
  1. author - 作者

  2. license - 许可证

  3. dependencies - 项目运行依赖(第三方包的信息,这里包含的包只要没有,项目就无法运行)

  4. devDependencies - 项目开发依赖(开发中会用到,在项目运行时不会用,例如:less,sass)

9. requeire加载机制

  1. 判断缓存中有没有,如果有,使用缓存中的内容

  2. 缓存中没有,那么表示第一次加载,加载完会缓存

  3. 判断模块名有没有带路径(./)

  4. 模块名中有路径,加载自定义模块(自己写的文件)const xx = require('./xx')

    1. 优先加载同名文件,加载一个叫做 xx 的文件
    2. 再次加载js文件,加载 xx.js 文件
    3. 再次加载json文件,加载 xx.json 文件
    4. 最后加载node文件,加载 xx.node文件
    5. 如果上述文件都没有,则报错 “Cannot find module ‘./xx’”
  5. 模块名没有路径优先加载核心模块如果没有核心模块,则加载第三方模块

  6. 加载第三方模块的查找方式

    1. 优先在当前文件夹的node_modules里面查找第三方模块
    2. 在当前文件夹的上级目录的node_modules里面查找第三方模块
    3. 继续向上层文件夹查找第三方模块

10. 开发属于自己的包

更多关于npm的命令:https://www.npmjs.cn/

10.1 规范的包结构

在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。

📂 - sy123
		📃 - package.json  (package.json包的配置文件)
		📃 - index.js      (入口文件)
		📃 - README.md     (说明文档)

一个规范的包结构,需要符合以下 3 点要求:

  1. 包必须以单独的目录而存在
  2. 包的顶级目录下要必须包含 package.json 这个包管理配置文件
  3. package.json 中必须包含 name,version,main 这三个属性,分别代表包的名字、版本号、包的入口。
    • name 包的名字,我们使用 require()加载模块的时候,使用的就是这个名字
    • version 版本,1.2.18
    • main 入口文件。默认是index.js 。如果不是,需要使用main指定

注意: 以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:

https://yarnpkg.com/zh-Hans/docs/package-json

10.2 开发属于自己的包

  • 初始化 package.json
{
  "name": "sy123",  // 包(模块)的名字,和文件夹同名。别人加载我们的包,找的就是这个文件夹
  "version": "1.0.0",
  "description": "This is a package by Laotang",
  "main": "index.js", // 别人加载我们的模块用,require加载的就是这里指定的文件
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [ // 在npm网站中,通过关键字可以搜索到我们的模块,按情况设置
    "laotang",
    "itcast",
    "test"
  ],
  "author": "Laotang", // 作者
  "license": "ISC" // 开源协议
}

注意,JSON文件不能有注释,下面加注释,是为了理解

关于更多 license 许可协议相关的内容,可参考 https://www.jianshu.com/p/23e61804d81e

  • index.js 中定义功能方法
// 别人加载的就是我的 index.js 
// 所以,必须在 index.js 中导出内容

function a() {
  console.log('aaa')
}

function b() {
  console.log('bbb')
}

module.exports = { a, b }
  • 编写包的说明文档

    包根目录中的 README.md 文件,是包的使用说明文档。通过它,我们可以事先把包的使用说明,以 markdown 的 格式写出来,方便用户参考。

    README 文件中具体写什么内容,没有强制性的要求;只要能够清晰地把包的作用、用法、注意事项等描述清楚即可。

10.3 发布npm包

# 切换npm源 - 切换镜像源为npm(不能发布到淘宝,所以必须切换镜像源为npm主站)
npm use npm
# 登录npm - 按照提示输入账号、密码(输入的密码是看不见的,正常)、邮箱、邮箱收到的一次性密码
npm login
# 发布 - 执行命令的文件夹,必须是包的根目录
npm publish

10.4 更新npm包

# 在package.json中version,升级版本号
# 推送不上去,就使用 npm login 重新登录一下
npm publish

10.5 删除已发布npm包

npm unpublish 包名 --force
  • 注意:
    1. 删除的包,在 24 小时内不允许重复发布(同名的包)
    2. npm unpublish 删除的包,在 24 小时内不允许重复发布
    3. 发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包!

11. 模块化

是一种编程方式,将整体打碎,编程一个个的模块,每个模块是独立的功能,把模块合到一起拼成完成的项目。

11.1 浏览器中使用模块

  1. AMD - 依赖前置。require.js

  2. CMD - 依赖就近。sea.js

    注意:非官方实现规范

11.2 NodeJs中使用模块

CommonJs,require引入,module.exports导出

  1. 自定义模块

    require('./要引入的js文件路径')

    来源:自己编写

  2. 核心模块

    require('包名')

    来源:nodejs自带

  3. 第三方模块

    require('包名')

    来源:需要下载

    npm install 包名
    npm i 包名
    npm i 包名 包名...
    npm i 包名@版本号
    # 下载开发依赖
    npm i -D 包名
    # 下载全局包
    npm i -g 包名
    

12. ES6模块化

是js官方退出,为了解决js在不同的平台没有同意模块化标准问题。

nodejs版本要在13版本以上,在package.json中声明 type: "module"

12.1 默认导入和导出

export default 要导出的内容

import 自定义名称 from ‘路径.js’

场景:导出一个内容

示例

导出文件 01-module.js

const PI = 3.1415926256252236
function fn() {
  console.log(111)
}
export default {
  PI,
  fn
}

导入文件 01-导入.js

import m1 from './01-module.js'
console.log(m1)

12.2 按需导入和导出

export let a = 1

import { 导出变量名 } from ‘路径.js’

示例

导出文件 02-module.js

// export 后面直接跟导出内容的完整的语句
export let a = 111
export function fn() {
  console.log(222)
}
// 这一个顶上面两个
export {
	a,
  fn
}
// 报错,因为没有提供接口(取数据数据名)
//export 1
//export a

导入文件 02-导入

// import { 导出的值(名字不能变,导入的是什么名,导出的就是什么名,导入也要是什么名) } from '路径'
// 如果要给引入的内容起别名使用 as
// 语法:导出名 as 别名
import { a as a1, fn } from "./02-module.js"

console.log(a1)
console.log(fn)

12.3 直接导入、不导出

import ‘./03-module.js’

示例

文件 03-module.js

for (let i = 0; i < 5; i++) {
	console.log(i)
}

导入文件 03-直接导入.js

import './03-module.js'

13. Promise

解决问题:回调地狱

13.1 回调地狱

多个异步任务是无法保证执行顺序的,当我们想让多个异步任务按照固定的顺序执行,写出来的代码就是回调地狱

回调地狱 - 示例

import fs from 'fs'

fs.readFile('./a.txt', 'utf-8', function (err, data) {
  if (err) return
  console.log(data)

  fs.readFile('./b.txt', 'utf-8', function (err, data) {
    if (err) return
    console.log(data)

    fs.readFile('./c.txt', 'utf-8', function (err, data) {
      if (err) return
      console.log(data)
    })
  })
})

13.2 Promise - 基本语法

  1. 定义Promise对象

    const p1 = new Promise((resolve,reject) => {
    	// 成功时执行resolve,比如成功获取服务端数据,读取文件内容成功。
      // 失败时执行reject,因为参数错误(权限不够)导致业务执行失败
      // resolve(想传给外面的数据,比如成果获取到服务数据或读取到文件内容)
      // reject(失败信息)
    })
    
  2. 使用

    p1.then(
    	// 必填,调用resolve时执行,参数就是resolve中的参数
    	res => {},
    	// 可选,调用reject时执行,参数就是reject中的参数
    	err => {}
    )
    

13.3 三种状态

  • 最初状态:pending,等待中,此时promise的结果为 undefined;
  • 当 resolve(value) 调用时,达到最终状态之一:fulfilled,(成功的)完成,此时可以获取结果value
  • 当 reject(error) 调用时,达到最终状态之一:rejected,失败,此时可以获取错误信息 error

当达到最终的 fulfilled 或 rejected 时,promise的状态就不会再改变了。

特点

当调用 resolve的时候,Promise 将到达最终的状态。 达到最终状态之后,Promise的状态就不会再改变了。

多次调用 resolve 函数,只有第一次有效,其他的调用都无效。

const fs = require('fs');

// 1. 创建 Promise 对象;(承诺)
let p = new Promise((resolve, reject) => {
  resolve(123);
  resolve(456); // 这次调用无效
});

// 2. 获取异步任务的结果
// p.then(函数1, [函数2]);
p.then(res => {
  console.log(res); // 123
}, err => {
  console.log(err);
});

13.4 同步异步

Promise构造函数,是同步的

then里面的函数,是异步的

// 同步
console.log(1)
const p1 = new Promise(resolve => {
  // 同步
  console.log(2)
  resolve()
  console.log(3)
})
p1.then(() => {
  // 异步
  console.log(4)
})
// 同步
console.log(5)

// 结果:12354

13.5 then方法的链式调用

  • 前一个then里面返回的字符串,会被下一个then方法接收到。但是没有意义;

  • 前一个then里面返回的Promise对象,并且调用resolve的时候传递了数据,数据会被下一个then接收到

  • 前一个then里面如果没有调用resolve,则后续的then不会接收到任何值

14. async 和 await 修饰符

ES6 — ES2015

async 和 await 是 ES2017 中提出来的。

异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题。

从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。

异步I/O不就是读取一个文件吗,干嘛要搞得这么复杂?异步编程的最高境界,就是根本不用关心它是不是异步。

async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案

ES2017 提供了async和await关键字。await和async关键词能够将异步请求的结果以返回值的方式返回给我们。

  • async 用于修饰一个 function
    • async 修饰的函数,总是返回一个 Promise 对象
    • 函数内的返回值,将自动包装在 resolved 的 promise 中
  • await 只能出现在 async 函数内
    • await 让 JS 引擎等待直到promise完成并返回结果
    • 语法:let value = await promise对象; // 要先等待promise对象执行完毕,才能得到结果
    • 由于await需要等待promise执行完毕,所以await会暂停函数的执行,但不会影响其他同步任务
  • 对于错误处理,可以选择在async函数后面使用 .catch() 或 在promise对象后使用 .catch()
const fs = require('fs');
// 将异步读取文件的代码封装
function myReadFile (path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf-8', (err, data) => {
            err ? reject(err) : resolve(data.length);
        });
    }).catch(err => {
        console.log(err);
    });
}

async function abc () {
    let a = await myReadFile('./a.txt');
    let b = await myReadFile('./b.txt');
    let c = await myReadFile('./c.txt');
    console.log(b);
    console.log(a);
    console.log(c);
}

abc();
错误处理

前提是得到几个Promise对象,代码如下:

let fs = require('then-fs');

let p1 = fs.readFile('./files/a.txt', 'utf-8');   
let p2 = fs.readFile('./files/bbb.txt', 'utf-8');  // 注意,这里故意写错路径
let p3 = fs.readFile('./files/c.txt', 'utf-8');

获取Promise的结果,可以通过 then 来获取。也可以通过 async 和 await 获取。

如果使用 then 获取结果,那么错误如何处理:在链式调用的尾端,加一个catch方法即可

// ----------------------  通过 then 获取结果       ----------------------------
p1.then(res => {
  console.log(res.length);
  return p2;
}).then(res => {
  console.log(res.length);
  return p3;
}).then(res => {
  console.log(res.length);
}).catch(err => {
  console.log(err);
})

如果使用async和await获取结果,最好的错误处理方案,就是使用 try ... catch ...

// ---------------------- 通过 async/await 获取结果 ----------------------------
async function abc() {
  try { // 尝试做一些事情
    let r1 = await p1; // 正常得到结果
    let r2 = await p2; // 这里出错了,就会抛出错误 throw err。
    let r3 = await p3;
    console.log(r1.length, r2.length, r3.length);
  } catch (e) {
    console.log(e); // catch这里,会抓住前面try里面抛出的错误
  }
}
abc();

15. 宏任务和微任务、事件循环

JavaScript是单线程的,也就是说,同一个时刻,JavaScript只能执行一个任务,其他任务只能等待。

15.1 为什么JavaScript是单线程的

js是运行于浏览器的脚本语言,因其经常涉及操作dom,如果是多线程的,也就意味着,同一个时刻,能够执行多个任务。

试想,如果一个线程修改dom,另一个线程删除dom,那么浏览器就不知道该先执行哪个操作。

所以js执行的时候会按照一个任务一个任务来执行。

15.2 为什么任务要分为同步任务和异步任务

试想一下,如果js的任务都是同步的,那么遇到定时器、网络请求等这类型需要延时执行的任务会发生什么?

页面可能会瘫痪,需要暂停下来等待这些需要很长时间才能执行完毕的代码

所以,又引入了异步任务。

  • 同步任务:同步任务不需要进行等待可立即看到执行结果,比如console
  • 异步任务:异步任务需要等待一定的时候才能看到结果,比如setTimeout、网络请求

16. 事件循环(Event Loop)

事件循环的比较简单,它是一个在 “JavaScript 引擎等待任务”,"执行任务"和"进入休眠状态等待更多任务"这几个状态之间转换的无限循环。

引擎的一般算法:

  1. 当有任务时:
    • 从最先进入的任务开始执行。
  2. 没有其他任务,休眠直到出现任务,然后转到第 1 步。

17. 任务队列

根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise 等API便是任务源。

在事件循环中,每进行一次循环的关键步骤如下:

  • 在此次循环中选择最先进入队列的任务(oldest task),如果有则执行(一次)
  • 检查是否存在 微任务(Microtasks),如果存在则不停地执行,直至清空 微任务队列(Microtasks Queue)
  • 更新 render(DOM渲染)
  • 以上为一次循环,主线程重复执行上述步骤

在上述循环的基础上需要了解几点:

  • JS分为同步任务和异步任务
  • 同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,宿主环境管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BZFDf1R5-1653472547308)(F:\黑马程序员\前端就业班\07-node.js\02-案例\项目前置课-03\文档\其他.assets\bV31Xm)]

17.1 宏任务

(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。

任务(代码)宏任务环境
script宏任务浏览器
事件宏任务浏览器
网络请求(Ajax)宏任务浏览器
setTimeout() 定时器宏任务浏览器 / Node
fs.readFile() 读取文件宏任务Node

比如去银行排队办业务,每个人的业务就相当于是一个宏任务;

17.2 微任务

微任务(microtask)是宏任务中的一个部分,它的执行时机是在同步代码执行之后,下一个宏任务执行之前。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOSCSokB-1653472547310)(F:\黑马程序员\前端就业班\07-node.js\02-案例\项目前置课-03\文档\其他.assets\image-20210418173900754.png)]

微任务包含:
Promise.then
process.nextTick(Node.js 环境)

比如一个人,去银行存钱,存钱之后,又进行了一些了操作,比如买纪念币、买理财产品、办信用卡,这些就叫做微任务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D1IICVgU-1653472547311)(F:\黑马程序员\前端就业班\07-node.js\02-案例\项目前置课-03\文档\其他.assets\image-20210326115124196.png)]

17.3 运行机制

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(执行栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务里的同步代码执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8S6A8DeZ-1653472547312)(F:\黑马程序员\前端就业班\07-node.js\02-案例\项目前置课-03\文档\其他.assets\image-20210623200912106.png)]

18. webpack 的基本使用

初级使用步骤

  1. 在项目根目录创建 webpack.config.js 文件

  2. 在这个文件内写一下代码

    const path = require('path')
    module.exports = {
      // 打包方式 - development 开发环境(编写程序的时候使用), production 生产环境(项目完成上线的时候)
      mode: 'development', // 决定了 main.js 编译的方式
      // 修改入口文件
      entry: './src/index1.js',
      // 修改出口文件(打包后的文件路径和文件名)
      output: {
        // 指定打包好后文件的路径
        path: path.join(__dirname, 'dist'),
        // 指定打包好后文件的文件文件夹名称
        filename: 'a.js'
      }
    }
    
  3. 在package.json中script内声明 "build": "webpack"

  4. 运行简化命令行 npm run build

  5. 在html中引入生成的main.js

注意

  1. 默认的打包入口文件为 src -> index.js
  2. 默认的输出文件路径为 dist -> main.js
  3. 可以在 webpack.config.js 中修改打包的默认约定

18.1 安装和配置webpack

目标:

  • 通过 webpack 处理 JS 代码的兼容性问题
  • 了解 webpack 的基本配置步骤。

安装 webpack 相关的两个包: webpack@5.58.2webpack-cli@4.9.0 (前面已经统一安装过了)

① 在项目根目录中,创建名为 webpack.config.js 的 webpack 配置文件,并初始化如下的基本配置:

module.exports = {
  // 打包模式
  mode: 'development', //  production:生成  development:开发
}

② 在 package.json 的 scripts 节点下,新增 dev 脚本如下:

"scripts": {
  "dev": "webpack"
},

③ 在终端中运行 npm run dev 命令,启动 webpack 进行项目的打包构建

18.2 打包结果

执行 npm run dev 命令后,会在项目根目录生成 dist文件夹,并在 dist 文件夹中生成 main.js

main.js 就是 jquery.js 和 index.js 的结合体。

index.html 中引入 main.js ,再看效果。

18.3 mode 的可选值

目标:清楚每个 mode 值的作用,知道每个值的具体应用场景。

mode 节点的可选值有两个,分别是:

① development

  • 开发环境
  • 不会对打包生成的文件进行代码压缩和性能优化
  • 打包速度快,适合在开发阶段使用

② production

  • 生产环境
  • 会对打包生成的文件进行代码压缩和性能优化
  • 打包速度很慢,仅适合在项目发布阶段使用

18.4 webpack.config.js的作用

webpack.config.js是什么

  • webpack.config.js 是 webpack 的配置文件。

作用

  • 告诉 webpack 怎么对项目进行打包。

被读取的时机

  • npm run dev 的时候(打包的时候),会先读取配置文件,再对项目进行打包处理。

18.5 webpack 中的默认约定

在 webpack 4.x 和 5.x 的版本中,有如下的默认约定:

① 默认的打包入口文件为 src -> index.js

② 默认的输出文件路径为 dist -> main.js

注意:可以在 webpack.config.js 中修改打包的默认约定

18.6 自定义打包的入口与出口

在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口。通过 output 节点指定打包的出口。

示例代码如下:

// 引入path 并解构出 join方法
const { join } = require('path');

module.exports = {
  // 打包模式
  mode: 'development', //  production:生成  development:开发
  // 入口文件
  entry: join(__dirname, 'src', 'index.js'),
  // 出口文件
  output: {
    path: join(__dirname, 'dist'), // 要使用绝对路径,否则报错
    filename: 'bundle.js',
  }
}

重新运行 npm run dev ,即可得到新的打包结果 bundle.js .

index.html 中引入新的打包结果 bundle.js。

19. webpack 中的插件

最常用的 webpack 插件有如下3个:

① clean-webpack-plugin

  • 每次打包时,自动清理 dist 目录

② webpack-dev-server

  • 类似于 node.js 阶段用到的 nodemon 工具
  • 每当修改了源代码,webpack 会自动进行项目的打包和构建

③ html-webpack-plugin

  • webpack 中的 HTML 插件
  • 可以通过此插件自定制 index.html 页面的内容

19.1 clean-webpack-plugin

作用:每次打包构建的时候,自动清理 dist 目录下的旧文件,保证 dist 目录的代码是最新的。

安装依赖包:clean-webpack-plugin@4.0.0 (前面已经统一安装过)

在 webpack.config.js 中增加配置:

// 1. 配置自动清理插件(在打包的时候,插件就会自动清理 dist 中没用的文件)
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const cleanPlugin = new CleanWebpackPlugin();

module.exports = {
  mode: 'development',
  // 其他项省略......
  // 插件配置
  plugins: [ cleanPlugin ]
}

重新运行 npm run dev ,即可将 dist 文件夹中没用的文件清理掉。

19.2 webpack-dev-server

19.2.1 基本使用

作用:可以让 webpack 监听项目源代码的变化,从而进行自动打包构建。

安装包 webpack-dev-server@4.3.1(前面已经统一安装过)

配置 webpack-dev-server

① 修改 package.json -> scripts 中的 dev 命令如下:

"scripts": {
  "dev": "webpack serve" // 注意这里是 serve ,不是 server
},

② 在 webpack.config.js 配置文件中,增加 devServer 节点对 webpack-dev-server 插件进行更多的配置

devServer: {
  port: 9000, // 实时打包所用的端口号
  open: true // 初次打包完成后,自动打开浏览器
}

③ 再次运行 npm run dev 命令,重新进行项目的打包

④ 在浏览器中访问 http://localhost:8080 地址,查看自动打包效果

注意:凡是修改了 webpack.config.js 配置文件,或修改了 package.json 配置文件,必须重启实时打包的服务器,否则最新的配置文件无法生效!

19.2.2 打包生成的文件哪儿去了?

① 不配置 webpack-dev-server 的情况下,webpack 打包生成的文件,会存放到实际的物理磁盘上

  • 严格遵守开发者在 webpack.config.js 中指定配置

  • 根据 output 节点指定路径进行存放

② 配置了 webpack-dev-server 之后,打包生成的文件存放到了内存中

  • 不再根据 output 节点指定的路径,存放到实际的物理磁盘上
  • 提高了实时打包输出的性能,因为内存比物理磁盘速度快很多
19.2.3 生成到内存中的文件该如何访问?
  • webpack-dev-server 生成到内存中的文件,默认放到了项目的根目录中,而且是虚拟的、不可见的。
  • 可以直接用 / 表示项目根目录,后面跟上要访问的文件名称,即可访问内存中的文件
  • 例如 /bundle.js 就表示要访问 webpack-dev-server 生成到内存中的 bundle.js 文件

所以,index.html 中应该这样引入js <script src="/bundle.js"></script>

19.3 html-webpack-plugin

html-webpack-plugin 是 webpack 中的 HTML 插件。

作用:自动把生成好的 bundle.js 注入到 HTML 页面中。

安装包 html-webpack-plugin@5.3.2 (前面已经统一安装过)

在webpack.config.js中配置 html-webpack-plugin

const HtmlPlugin = require('html-webpack-plugin');
const htmlPlugin = new HtmlPlugin({
  template: path.join(__dirname, 'public', 'index.html'), // public中,你的html叫什么
  filename: 'index.html' // 打包后的html叫什么(这个文件会生成到dist文件夹)
});

module.exports = {
  mode: 'development',
  // 其他项省略......
  // 插件配置
  plugins: [ cleanPlugin, htmlPlugin ]
}

其他说明:

① 通过 HTML 插件复制到项目根目录中的 index.html 页面,也被放到了内存中,所以看不到

② HTML 插件在生成的 index.html 页面,自动注入了打包的 bundle.js 文件

20. webpack 中的 loader

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值