http://nodejs.cn/learn
# 知识回顾
很早以前,我们就以前学习了 JavaScript,并且了解到 JS 代码不仅可以操作 DOM 和 BOM 等,还可以在浏览器中运行。
通过上图,我们可以发现:
- 浏览器是 JavaScript 前端运行环境
- V8 引擎可以解析和执行 JavaScript 代码
# 一、Node.js
通过前面的回顾,我们可以思考一个问题:JavaScript 能否做后端开发?
答案是肯定的。
# 1、基本概述
官方介绍:
Node.js® (opens new window) 是一个基于 Chrome V8 引擎 的 JavaScript 后端运行环境。
特点:
Node.js 的出现,让 JavaScript 可以脱离浏览器限制,像其他编程语言一样直接在计算机上使用。
理解
- 浏览器中运行 index.js 文件
<script src="./index.js"></script>
- node.js 中运行 index.js 文件
node ./index.js
通过上图,我们可以发现:
- Node.js是 JavaScript 的后端运行环境
- 在 Node.js 中是无法调用 DOM、BOM 等浏览器内置 API 的。
同浏览器运行环境一样,Node.js 提供了一些基础的功能和 API。当然,基于它的基础功能和 API 而开发的工具和框架如雨后春笋,层出不穷。如:
- Express 框架(快速构建 Web 应用)
- Electron 框架(构建跨平台的桌面应用)
- 等等
# 2、架构图
JS 代码跑在 V8 引擎上,Node.js 内置的 fs、http 等核心模块通过 C++ Bindings 调用 libuv、c-ares、llhttp 等 C/C++类库,从而接入操作系统提供的平台能力
执行流程
从队列的角度,我们编写的 js 代码经过 v8 引擎,再通过 node.js 的 Bindings 将任务放在 Libuv 的事件循环中。
libuv 提供了 事件循环、文件系统读写、网络 IO、线程池等内容
# 3、全局对象 🎈
与浏览器中的 window、console、Object 等一样,node 环境中也有自己的全局变量 Globals (opens new window):
- __dirname、__filename
- module、require()、exports —— 模块化
- setInterval()、setTimeout()、setImmediate() —— 定时器函数
- ……
setImmediate() 和 process.nextTick()
// 立即执行
setImmediate(() => {
console.log('setImmediate')
})
// 执行函数
process.nextTick(() => {
console.log('nextTick')
})
思考:window 和 global 的区别
# 4、版本管理 ✨
一般我们可以直接在官网下载 Node.js 的 LTS 长期稳定版本,并且较为流行的版本为 14.x
Node 官方:下载地址 (opens new window)
node 的相关命令:
# 1、查看版本
node -v
# 或
nvm list
# 2、运行 (对比:Tomcat7 run)
node [要执行的文件]
当然我们也可以通过使用包管理工具 nvm 来安装 Node.js 运行环境。
node 官方描述:包管理器 (opens new window)
按照 nvm 在 github 上的描述 (opens new window),若要在 window 中使用安装 nvm,需要我们使用替代方案:
nvm-windows (opens new window)(star 😂 最多)、nodeist (opens new window)、nvs (opens new window)
nvm 的相关命令:
# 查看nvm可安装的Node.js版本
nvm list available
nvm install 16 # 具体版本:nvm install 16.13.1
nvm use 16
nvm uninstall 16
# 查看已下载的Node.js版本、正在所有的Node.js版本
nvm list
# 查看命令
nvm which # 其中 nvm root 表示查看安装目录
版本切换与项目开发:
在真正的项目开发中,我们还可以使用 nvm 来保证运行版本一致:
项目应用
提示:
.nvmrc
在 nvm-windows (opens new window) 中是不受支持的,具体可以查看 issue (opens new window)
先在项目根目录下创建 .nvmrc
- .nvmrc
v16.13.0
任何后续别人使用你的项目时
- 先切换版本
nvm use # 自动识别.nvmrc文件
# 5、拓展知识
由于 node 是安装在计算机上的,所以是有必要了解一些终端命令的。
① 终端命令
- tab 键 —— 自动补全
- esc 键 —— 清空当前的输入命令
- cls 命令 —— 清空 PowShell(终端)
- ↑ 键 —— 快速定位到上一条命令
② 代码提示
下载@types/node 包:
cnpm i @types/node -D
③ 输入输出
node ./index.js env=development
console.log(process.argv)
console.log(process.argv[2])
④ 终端
node 中也有浏览器中的 Console 一样的 REPL
# 进入REPL
node
# 二、模块化开发
JavaScript 中的模块化规范,是符合前端发展规律的,即可以将程序划分为一个个小的结构,又可以方式参数变量、函数、对象等发生全局污染(作用域)
浏览器中执行 ✍ 模块化代码
浏览器中可以借助 webpack 等工具实现对 CommonJS 代码的转换;
浏览器本身是支持 ESModule 代码;
<script src="./index.js" type="module"></script>
Node 环境中执行 ✍ 模块化代码
乖乖的使用 CommonJS 规范吧 😂
实在不爽,使用 node v13.2.0 之后的版本,并进行如下操作:
{
// 在package.json加入
"type": "module"
}
# 1、基础方案
方案示例
// 利用函数作用域
const moduleA = (function () {
let age = 20
// ……
return {
age
}
})()
# 2、早期方案
- AMD
常见的库有 require.js (opens new window) 和 curl.js
- CMD
- CommonJS:
module.exports = {}
-const m1 = require('./text.js')
# 3、ES6 方案
- ES6 Module(ES6)👏:
export default A
-import A from './A.js'
# 4、混合使用 ✨
提示
从 Vue CLI 3.x 版本开始,默认创建的项目使用的是 ES Module 规范
并且 ES Module 是 JavaScript 的官方模块化规范
两者的混合使用思考?
为什么在 vue-cli 项目中:
- vue.config.js 中出现
const { defineConfig } = require('@vue/cli-service')
- main.js 中出现
import Vue from 'vue'
现代的构建工具(如 Webpack)通常支持同时使用多种模块化规范,是因为:
- 兼容性:早期 CommonJS 是 Node.js 最早采用的模块化规范,导致某个库或模块是基于 CommonJS 编写的
# 三、CJS 模块化
其实细微的回想,webpack 也是遵循 CommonJs 规范(简称:CJS)的。
# 1、基础认知
module.exports 与 exports 的区别
每一个模块中都有一个 module 对象,它存储了和当前模块有关的信息。其要注意的是里面的exports属性
,里面的内容为对象的形式存在。
为了简化 module.exports 向外共享成员的代码量,Node 提供了 exports 对象。(即:一个存储的是对象,一个本身就是对象)
module.exports
指向的是一个对象,exports
指向的是对象的属性
关于 require() 方法
使用 require()方法导入模块时,导入的结果永远以 module.exports 指向的对象为准(对象的引用赋值)。为了防止混乱,建议不要同时使用。
require()方法不仅可以导入自定义模块,还可以导入:
// node内置模块
const path = require('path')
// 第三方模块(会自动查找node_modules中的axios文件)
const path = rquire('axios')
# 2、使用示例
const sayName = () => {
console.log('我叫Lencamo')
}
// 方式一:module.exports(推荐)
module.exprots = {
say: sayName, //或者简写✨为 sayNam,
age: 20
}
// 方式二:exports
exports.say = sayName
exports.age = 20
// 使用1
const m1 = require('./test.js')
m1.say()
m1.age
// 使用2(推荐)
const { say, age } = require('./test.js')
say()
age
时间格式化
function dateFormat(dtStr) {
// 这个注意区别new Date()
const dt = new Date(dtStr)
const y = dt.getFullYear()
const m = fixZero(dt.getMonth() + 1)
const d = fixZero(dt.getDate())
const hh = fixZero(dt.getHours())
const mm = fixZero(dt.getMinutes())
const ss = fixZero(dt.getSeconds())
// 使用【模块🚩字符串】 来拼接
// return `YYYY-MM-DD hh:mm:ss`;
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
// 自定义补零函数
function fixZero(n) {
return n > 9 ? n : '0' + n
}
// 共享模块作用域
module.exports = {
dateFormat
}
const mytime = require('./mytime')
// 使用自定义的模块
const t = new Date()
// 1、普通打印
console.log(t) // 2022-04-15T07:27:19.678Z
// 2、格式化打印
console.log(mytime.dateFormat(t)) // 2022-04-15 15:27:19
拓展:
当然还可以使用一些第三方包:moment、dayjs
const moment = require('moment')
const dayjs = require('dayjs')
// 使用包
console.log(moment().format('YYYY-MM-DD HH:MM:SS')) //2022-04-15 15:43:22
console.log(dayjs().format('YYYY-MM-DD HH-mm-ss')) // 2022-04-15 15:27:19
# 3、模块加载机制
- 模块导入的缺点:模块导入是同步执行的
浏览器加载 js 文件是需要先从服务器中将将文件下载下来的;
- 模块在第一次加载时会被 缓存 和 执行
多个重复的 require()只会加载一次 (这个与每个模块对象 module 中的属性 loaded 有关 😂)
内置模块的加载优先级更高
使用 require 导入自定义模块时,可以省略后缀名.js
模块会自动进行依次查找
.js
、.json
、.node
后缀名的文件;
若找不到则会包它当成一个目录,依次查找该目录下的
index.js
、index.json
、index.node
。
如果还找不到的话,就会报错
使用 require 导入第三方模块时,会先查找 node_modules 文件夹。若没有,则会一步一步的往父层查找 node_modules 文件夹。
若使用 require 导入的多个模块间存在循环引入,其执行顺序采用的是 深度优先算法 DFS(图结构)
# 四、ESM 模块化
其实细微的回想,vue 也是遵循 ES6 模块化 规范的。
# 1、基础认知
启用 ESModule 🤔 规范
- 新版本的 Node.js 中启用 ES6 Module 功能
{
// 在package.json加入
"type": "module"
}
- 浏览器中启用 ES6 Module 功能
<script src="./index.js" type="module"></script>
- 开发环境中直接使用 webpack 即可
ESModule 解析流程
https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/
提示
使用 ESModule 规范时,import 导入模块时是不可以省略后缀名.js 的(但在正式的项目开发中,webpack 可以解决这个问题)
# 2、使用示例
① export default 方式
// 一个模块只能有一个
export default A
//
import AAA from './A.js'
使用示例
- A.js
const sayName = () => {
console.log('我叫Lencamo')
}
const age = 22
// 1、exports default(单个对象)
export default {
sayName,
age
}
import A from './A.js'
A.sayName
console.log(A.age)
② export 方式
export { a, b }
//
// 方式1
import { a as a1, b } from './more.js'
// 方式2
import * as A from './more.js'
A.a
console.log(A.b)
使用示例
- More.js
const sayName = () => {
console.log('我叫Lencamo')
}
const age = 22
// 2、export(多个成员【按需导出】)
export const password = 1234
export { sayName, age }
import { sayName, password } from './more.js'
sayName()
console.log(password)
# 3、导入导出 ✨
- index.js
import { a, b } from './foo.js'
import { c } from './bee.js'
export { a, b, c }
// 综合写法1
export { a, b } from './foo.js'
export { c } from './bee.js'
// 综合写法2
export * from './foo.js'
export * from './bee.js'
# 5、import 函数
拓展
在 ES11 中新增了一个 import.meta
元数据属性的对象
let flag = true
if (flag) {
// 动态加载模块
import('./foo.js').then((res) => {
console.log(res.sayName())
console.log(res.age)
})
}