1. 什么是 Node.js
Node.js
:是一个基于Chrome V8 引擎的 JavaScript
运行环境
Node.js
官方地址: http://nodejs.org/zh-cn
注意:
- 浏览器是
JavaScript
的前端运行环境。 Node.js
是JavaScript
的后端运行环境。Node.js
中无法调用DOM
和BOM
等浏览器内置API
2. 使用node命令执行JS文件
操作步骤:
- 打开终端
- 注意路径,进入到你要运行的项目文件目录下
- 输入
node 要执行的js文件
注意:
- 执行文件的时候,需要保证
node xxx.js
这种格式 - 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. 自定义模块
导出文件 - 导出
方式一:
module.exports = { 变量名, 方法名,…}
方式二:
exports.xx = xx
导入文件 - 导入
引入:let xxx = require(‘地址URL’)
console.log(xxx.变量名)
示例:
- 导出内容 (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
- 导入内容 (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
- 注意:
- 初始化之后,会在项目目录中生成 package.json 的文件。
- package name 默认使用当前文件夹 当做 包的名字
- 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"
}
}
-
name
- 项目名,不能有中文 -
version
- 版本号,只能数字和点 -
description
- 描述 -
main
- 项目的入口文件是哪一个文件 -
scripts
- 声明简化命令
{
"简化命令": "真实命令(命令行能用的名)",
"start": "node ./index.js",
// "start": "ipconfig"
}
// 执行简化命令,在命令行中输入 npm run 简化命令
// start特殊,start可以 npm run start 也可以 npm start
-
author
- 作者 -
license
- 许可证 -
dependencies
- 项目运行依赖(第三方包的信息,这里包含的包只要没有,项目就无法运行) -
devDependencies
- 项目开发依赖(开发中会用到,在项目运行时不会用,例如:less,sass)
9. requeire加载机制
-
判断缓存中有没有,如果有,使用缓存中的内容
-
缓存中没有,那么表示第一次加载,加载完会缓存
-
判断模块名有没有带路径(./)
-
模块名中有路径,加载自定义模块(自己写的文件)
const xx = require('./xx')
- 优先加载同名文件,加载一个叫做 xx 的文件
- 再次加载js文件,加载 xx.js 文件
- 再次加载json文件,加载 xx.json 文件
- 最后加载node文件,加载 xx.node文件
- 如果上述文件都没有,则报错 “Cannot find module ‘./xx’”
-
模块名没有路径,优先加载核心模块,如果没有核心模块,则加载第三方模块
-
加载第三方模块的查找方式
- 优先在当前文件夹的node_modules里面查找第三方模块
- 在当前文件夹的上级目录的node_modules里面查找第三方模块
- 继续向上层文件夹查找第三方模块
- …
10. 开发属于自己的包
更多关于npm的命令:https://www.npmjs.cn/
10.1 规范的包结构
在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。
📂 - sy123
📃 - package.json (package.json包的配置文件)
📃 - index.js (入口文件)
📃 - README.md (说明文档)
一个规范的包结构,需要符合以下 3 点要求:
- 包必须以单独的目录而存在
- 包的顶级目录下要必须包含 package.json 这个包管理配置文件
- 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
- 注意:
- 删除的包,在 24 小时内不允许重复发布(同名的包)
- npm unpublish 删除的包,在 24 小时内不允许重复发布
- 发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包!
11. 模块化
是一种编程方式,将整体打碎,编程一个个的模块,每个模块是独立的功能,把模块合到一起拼成完成的项目。
11.1 浏览器中使用模块
-
AMD - 依赖前置。require.js
-
CMD - 依赖就近。sea.js
注意:非官方实现规范
11.2 NodeJs中使用模块
CommonJs,require引入,module.exports导出
-
自定义模块
require('./要引入的js文件路径')
来源:自己编写
-
核心模块
require('包名')
来源:nodejs自带
-
第三方模块
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 - 基本语法
-
定义Promise对象
const p1 = new Promise((resolve,reject) => { // 成功时执行resolve,比如成功获取服务端数据,读取文件内容成功。 // 失败时执行reject,因为参数错误(权限不够)导致业务执行失败 // resolve(想传给外面的数据,比如成果获取到服务数据或读取到文件内容) // reject(失败信息) })
-
使用
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 步。
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 的基本使用
初级使用步骤
-
在项目根目录创建 webpack.config.js 文件
-
在这个文件内写一下代码
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' } }
-
在package.json中script内声明
"build": "webpack"
-
运行简化命令行
npm run build
-
在html中引入生成的main.js
注意
- 默认的打包入口文件为 src -> index.js
- 默认的输出文件路径为 dist -> main.js
- 可以在 webpack.config.js 中修改打包的默认约定
18.1 安装和配置webpack
目标:
- 通过 webpack 处理 JS 代码的兼容性问题
- 了解 webpack 的基本配置步骤。
安装 webpack 相关的两个包: webpack@5.58.2
和 webpack-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 文件