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 来保证运行版本一致:

项目应用

提示:.nvmrcnvm-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

使用教程:阮一峰 (opens new window)

  • 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.jsindex.jsonindex.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)
  })
}
更新于 : 8/7/2024, 2:16:31 PM