起步

# 起步

Node.js 是一个基于 Google V8引擎的、跨平台的JavaScript运行环境(不是一门编程语言)

# 安装与运行

https://nodejs.org/zh-cn/ (opens new window)

image.png

建议使用LTS版本,一键傻瓜式下载安装

简单写一个Node.js的程序

index.js用来读取文件package.json文件

const { readFile } = require('fs');
    readFile('./package.json', {encoding: 'utf-8'},(err, data)=> {
    if(err){
        throw err;
    }
    console.log(data)
});

运行程序

node index.js

image.png

# 版本管理

node的版本那么多, 在同一个设备上如何快速切换Node.js 版本?

可以使用版本管理工具

  • n: 一个npm 全局的开源包,是依赖npm 来全局安装、使用的
  • fnm: 快速简单,兼容性支持 .node-version和.nvmrc文件
  • nvm: 独立的软件包,Node Version Manager

nvm用的比较多(是macOS平台的软件) Windows可以下载nvm-windows (opens new window)

详细教程可见https://www.runoob.com/w3cnote/nvm-manager-node-versions.html (opens new window)

# 特点

image.png

# 异步I/O

当Node.js执行I/O操作时,会在响应返回并恢复操作,而不是阻塞线程并浪费CPU(循环等待)

image.png

这里的I/O不仅指文件的读写,也包括对网络的请求,对数据库的操作等

代码编写顺序与执行顺序无关,看下面这例子

const { readFile } = require("fs");
readFile("./node基础/package.json", { encoding: "utf-8" }, (err, data) => {
  if (err) {
    throw err;
  }
  console.log(data);
});

console.log('我是在最下面编写的代码')

image.png

# 单线程

Node.js 保持了JavaScript 在浏览器中单线程的特点

优点:

  • 不用处处在意状态同步问题,不会发生死锁
  • 没有线程上下文切换带来的性能开销

缺点:(解决方案会在下一篇博文中说明)

  • 无法利用多核CPU
  • 错误会引起整个应用退出,健壮性不足
  • 大量计算占用导致CPU,无法继续执

# 多进程的浏览器

浏览器是多进程,JS引擎单线程

  • Browser 进程:浏览器主进程,只有一个

  • 插件进程:插件使用时才创建

  • GPU 进程:最多一个用于3D 绘制

  • 渲染进程:页面渲染、JS执行、事件处理

    • GUI 渲染线程
    • JS 引擎线程 (V8)
    • 事件触发线程
    • 定时器触发线程
    • 异步请求线程

image.png

# 跨平台

兼容 Windows 和 *nix 平台,主要得益于在操作系统与 Node 上层模块系统之间构建了一层平台架构

image.png

底层都是C/C++写的,应用层才是JavaScript写的

# 应用场景

Node.js在大部分领域都占有一席之地,尤其是I/O密集型的任务(而非计算密集型任务)

  • Web应用:Express / Koa
  • 前端构建:Webpack
  • GUI 客户端软件:VSCode / 网易云音乐
  • 其它:实时通讯、爬虫、CLI 等...

# 模块化机制

  1. What 何为模块化?

根据功能或业务将一个大程序拆分成互相依赖的小文件,再用简单的方式拼装起来

  1. Why 为什么模块化?无模块化问题
  • 所有 script 标签必须保证顺序正确,否则会依赖报错
  • 全局变量存在命名冲突,占用内存无法被回收
  • IIFE/namespace 会导致代码可读性低等诸多问题

# CommonJS 规范

Node.js 支持CommonJS 模块规范,采用同步机制加载模块

image.png

greeting.js导出模块

const prefix = "hello";
const sayHi = function () {
  console.log(`${prefix} world`);
};

module.exports = {
  sayHi,
};

index.js导入模块

const { sayHi } = require("./greeting");
sayHi();

image.png

# 机制

我们来研究一下模块化的机制

导出模块的时候不仅可以这样

module.exports = {
  sayHi,
};

也可以这样

exports.sayHi = sayHi

为什么可以这样呢? 在node中exports和module.exports指向的是同一块内存引用

exports = module.exports = {}

CommonJS 中 exportsrequiremodule__filename__dirname 变量 到底是什么

我们在暴露我们的模块的时候node会在外面包一层函数,会注入一些模块变量,所以我们可以在模块中直接使用

我们将刚刚的sayHi函数改成一个箭头函数,输出arguments(因为箭头函数没有argumengts,所以会向外查找)

const sayHi = () => {
  console.log(arguments);
};

exports.sayHi = sayHi;

image.png

这五个参数分别对应着exportsrequiremodule__filename__dirname

所以说Node在外面包裹的一个函数就是这样就是这样

function(exports,require,module,__filename,__dirname){
    const m = 1;
    moudule.exports.m = m;
}

# 加载方式

  1. 加载内置模块
require('fs')
  1. 加载相对、绝对路径的文件模块
// 绝对
require('/User/.../file.js')
// 相对
require('./file.js')
  1. 加载npm包
require('lodash')

# npm 包查找原则

require('lodash')

image.png

  1. 当前目录 node_modules
  2. 如果没有,父级目录的 node_modules
  3. 如果没有,沿着路径向上递归,直到根目录下 node_modules
  4. 找到之后会加载package.json main 指向的文件,如果没有package.json 则依次查找index.js、index.json、index.node

# npm 缓存

require.cache 中缓存着加载过的模块,缓存的原因:同步加载

  1. 文件模块查找耗时,如果每次require 都需要重新遍历查找,性能会比较差;

  2. 在实际开发中,模块可能包含副作用代码

有缓存

const mod1 = require("./foo");
const mod2 = require("./foo");
console.log(mod1 === mod2); // true

无缓存

function requireUnChched(module) {
  delete require.cache[require.resolve(module)];
  return require(module);
}
const mod3 = requireUnChched("./foo");
console.log(mod1 === mod3); // false

# 其他模块化规范

  • AMD 是RequireJS 在推广过程中规范化产出,异步加载,推崇依赖前置;
  • CMD 是SeaJS 在推广过程中规范化产出,异步加载,推崇就近依赖;
  • UMD (Universal Module Definition) 规范,兼容AMD 和CommonJS 模式
  • ES Modules (ESM),语言层面的模块化规范,与环境无关,可借助babel 编译

image.png

image.png

# ES Modules (ESM)

  • ESM 是在ES6 语言层面提出的一种模块化标准;
  • ESM中主要有importexport两个关键词,不能console打印两个关键词
  • 可以实现Tree Shaking

在node环境中如何使用ESM呢?将文件名后缀改成.mjs即可

image.png

不想改文件名后缀,也可以在项目的package.json中加一个type=module *.js

还可以使用babel编译转成ES5后使用(浏览器、node)

# CommonJS VS ESM

  • CommonJS 模块输出的是一个值的拷贝;ESM 模块输出的是值的引用
  • CommonJS 模块是运行时加载;ESM 模块是编译时输出(提前加载)
  • 可以混用,但是不建议( import commonjs || import 中require)

同样的代码在使用不同的模块化机制输出可能不用,我们看下面的例子

# CommonJS:值的拷贝

lib.js

let counter = 3
function addCounter() {
  counter++
}
module.exports = {
  counter,
  addCounter
}

main.js

const { counter, addCounter } = require("./lib");

console.log(counter); // 3
addCounter();
console.log(counter); // 3

# ESM:值的引用

lib.mjs

export let counter = 3
export function addCounter() {
  counter++
}

mian.mjs

import { counter, addCounter } from "./lib.mjs";

console.log(counter); // 3 
addCounter();
console.log(counter); // 4

如果上面的counter定义为一个对象,比如

let countet = { number: 3}

此时,两种模块化的结果就是一样的

# 常用模块介绍

更多内容请见官网 https://nodejs.org/en/ (opens new window)

文件操作模块 image.png

路径模块 image.png

os模块 image.png

# 包管理机制

# npm

NPM 是Node.js 中的包管理器,提供了安装、删除等其它命令来管理包

# 常用命令

npm init
npm config
npm run [cmd]
npm install [pkg]
npm uninstall [pkg]
npm update [pkg]
npm info [pkg]
npm publish

image.png

# package.json

  • name 包名称
  • version 版本号
  • main 入口文件
  • scripts 执行脚本
  • dependencies 线上依赖
  • devDependencies 开发依赖
  • repository 代码托管地址

image.png

更多 https://docs.npmjs.com/cli/v7/configuring-npm/package-json (opens new window)

# 版本命名规范

  • 1.0.0 Must match version exactly
  • >1.0.0 Must be greater than version
  • >=1.0.0 etc
  • ~1.2.0 "Approximately equivalent to version" See semver (opens new window)
  • ^1.2.0 "Compagble with version" See semver (opens new window)
  • 1.2.x 1.2.0, 1.2.1, etc., but not 1.3.0
  • * Matches any version

npm semver check https://semver.npmjs.com/ (opens new window)

# 第三方依赖

  • hip://... See 'URLs as Dependencies' below
  • git... See 'Git URLs as Dependencies' below
  • user/repo See 'GitHub URLs' below
  • path/path/path See Local Paths (opens new window) below

# 依赖

  • dependencies 业务依赖,应用发布后正常执行所需要的包
  • devDependencies 开发依赖,只用于开发环境
  • peerDependencies 同等依赖,比如一个webpack 插件依赖特定版本的webpack
  • bundledDependencies 打包依赖(npm run pack),必须已经在devDep 或者dep声明过
  • optionalDependencies 可选依赖

# 私有npm

如果不想公开一些包,可以用私有npm

  • 镜像公司内部私有npm
  • 镜像设置 npm config set registry=hips://bnpm.byted.org

# 其他

早期的包管理机制问题比较多

image.png

旧包管理机制的一些特性

  • 并行安装
  • 扁平管理
  • 锁文件(lockfile)
  • 缓存优化

新的解决方案

  • npm7 | yarn => lock/扁平/缓存...
  • pnpm => monorepo/硬、符号链接/安全性高...

# 参考

上次更新: 2022/4/21 22:30:43