起步
# 起步
Node.js 是一个基于 Google V8引擎的、跨平台的JavaScript运行环境(不是一门编程语言)
# 安装与运行
https://nodejs.org/zh-cn/ (opens new window)
建议使用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
# 版本管理
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)
# 特点
# 异步I/O
当Node.js执行I/O操作时,会在响应返回并恢复操作,而不是阻塞线程并浪费CPU(循环等待)
这里的I/O不仅指文件的读写,也包括对网络的请求,对数据库的操作等
代码编写顺序与执行顺序无关,看下面这例子
const { readFile } = require("fs");
readFile("./node基础/package.json", { encoding: "utf-8" }, (err, data) => {
if (err) {
throw err;
}
console.log(data);
});
console.log('我是在最下面编写的代码')
# 单线程
Node.js 保持了JavaScript 在浏览器中单线程的特点
优点:
- 不用处处在意状态同步问题,不会发生死锁
- 没有线程上下文切换带来的性能开销
缺点:(解决方案会在下一篇博文中说明)
- 无法利用多核CPU
- 错误会引起整个应用退出,健壮性不足
- 大量计算占用导致CPU,无法继续执
# 多进程的浏览器
浏览器是多进程,JS引擎单线程
Browser 进程:浏览器主进程,只有一个
插件进程:插件使用时才创建
GPU 进程:最多一个用于3D 绘制
渲染进程:页面渲染、JS执行、事件处理
- GUI 渲染线程
- JS 引擎线程 (V8)
- 事件触发线程
- 定时器触发线程
- 异步请求线程
# 跨平台
兼容 Windows 和 *nix 平台,主要得益于在操作系统与 Node 上层模块系统之间构建了一层平台架构
底层都是C/C++写的,应用层才是JavaScript写的
# 应用场景
Node.js在大部分领域都占有一席之地,尤其是I/O密集型的任务(而非计算密集型任务)
- Web应用:Express / Koa
- 前端构建:Webpack
- GUI 客户端软件:VSCode / 网易云音乐
- 其它:实时通讯、爬虫、CLI 等...
# 模块化机制
- What 何为模块化?
根据功能或业务将一个大程序拆分成互相依赖的小文件,再用简单的方式拼装起来
- Why 为什么模块化?无模块化问题
- 所有
script
标签必须保证顺序正确,否则会依赖报错 - 全局变量存在命名冲突,占用内存无法被回收
IIFE
/namespace
会导致代码可读性低等诸多问题
# CommonJS 规范
Node.js 支持CommonJS 模块规范,采用同步机制加载模块
greeting.js导出模块
const prefix = "hello";
const sayHi = function () {
console.log(`${prefix} world`);
};
module.exports = {
sayHi,
};
index.js导入模块
const { sayHi } = require("./greeting");
sayHi();
# 机制
我们来研究一下模块化的机制
导出模块的时候不仅可以这样
module.exports = {
sayHi,
};
也可以这样
exports.sayHi = sayHi
为什么可以这样呢? 在node中exports和module.exports指向的是同一块内存引用
exports = module.exports = {}
CommonJS 中 exports
、require
、module
、__filename
、__dirname
变量 到底是什么
我们在暴露我们的模块的时候node会在外面包一层函数,会注入一些模块变量,所以我们可以在模块中直接使用
我们将刚刚的sayHi函数改成一个箭头函数,输出arguments(因为箭头函数没有argumengts,所以会向外查找)
const sayHi = () => {
console.log(arguments);
};
exports.sayHi = sayHi;
这五个参数分别对应着exports
、require
、module
、__filename
、__dirname
所以说Node在外面包裹的一个函数就是这样就是这样
function(exports,require,module,__filename,__dirname){
const m = 1;
moudule.exports.m = m;
}
# 加载方式
- 加载内置模块
require('fs')
- 加载相对、绝对路径的文件模块
// 绝对
require('/User/.../file.js')
// 相对
require('./file.js')
- 加载npm包
require('lodash')
# npm 包查找原则
require('lodash')
- 当前目录 node_modules
- 如果没有,父级目录的 node_modules
- 如果没有,沿着路径向上递归,直到根目录下 node_modules
- 找到之后会加载package.json main 指向的文件,如果没有package.json 则依次查找index.js、index.json、index.node
# npm 缓存
require.cache 中缓存着加载过的模块,缓存的原因:同步加载
文件模块查找耗时,如果每次require 都需要重新遍历查找,性能会比较差;
在实际开发中,模块可能包含副作用代码
有缓存
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 编译
# ES Modules (ESM)
- ESM 是在ES6 语言层面提出的一种模块化标准;
- ESM中主要有
import
、export
两个关键词,不能console
打印两个关键词 - 可以实现Tree Shaking
在node环境中如何使用ESM呢?将文件名后缀改成.mjs
即可
不想改文件名后缀,也可以在项目的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)
文件操作模块
路径模块
os模块
# 包管理机制
# 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
# package.json
- name 包名称
- version 版本号
- main 入口文件
- scripts 执行脚本
- dependencies 线上依赖
- devDependencies 开发依赖
- repository 代码托管地址
更多 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
# 其他
早期的包管理机制问题比较多
旧包管理机制的一些特性
- 并行安装
- 扁平管理
- 锁文件(lockfile)
- 缓存优化
新的解决方案
- npm7 | yarn => lock/扁平/缓存...
- pnpm => monorepo/硬、符号链接/安全性高...
# 参考
- 字节跳动青训营PPT + 视频
- 《深入浅出Node.js》
- Node中文官网 https://nodejs.org/zh-cn/ (opens new window)
- 菜鸟教程nvm (opens new window)