node 简介
node.js 是⼀个 JS 的服务端运⾏环境,基于 V8,是在 JS 语⾔规范的基础上,封装了⼀些服务端的 runtime,能够简单实现⾮常多的业务功能。在 2009 年 (第一版 npm 被创建) 诞生之初是为了实现高性能的 web 服务器,再后来 node.js 慢慢演化为了一门服务端 “语言” commonjs 是一个 规范,node.js 是 cjs 的实现
引入 node 作用
跨平台开发: PC web H5 RN Weex
后端开发: API, RPC
前端开发: 前端工具链
工具开发: 脚本、脚手架、命令行。
举例
压缩: UglifyJS、JSMin
管理: npm、yarn、bower
模块系统: Commonjs, ESM
模块构建: Babel、Browserify、Webpack、Gulp、Grunt
生成器: yeoman、slush、CRA、CLI
node 缺陷
单线程很脆弱,但是可以通过 cluster / pm2 多核并发实现负载均衡
node 对 MongoDB、Mysql、redis 的支持比较好,对 neo4j、tigerGraph 的支持比较差
安全问题
vs 浏览器
Node 环境中是没有 DOM
, BOM
, 同样的,浏览器中也没有 fs
, path
这些模块。
事件循环
node 的事件循环
浏览器: 微任务、宏任务、raf、 render、 requestIdleCallback
cjs
和 esm
Node.js 使用 CommonJS 模块系统,而在浏览器中我们开始看到正在实施的 ESM 标准。
内核
npm npm install 工作流程
npm CI
Continuous Integration (持续集成),npm 从 5.7.0 版本开始引入的一个命令,专门为自动化的持续集成环境设计。和 install 的不同点
必须要有 package-lock.json 文件
且下载完全依赖该文件
会删除 node_modules
如果和 package.json 冲突,则直接报错
只能一次性安装
永不改写 package.json 和 package-lock.json 文件
Dependencies
dependencies: 项目依赖 (lodash (debounce, deepMerge))
devDependencies: 开发依赖 (webpack, rollup, jest)
peerDependencies: 同版本依赖。比如 vue 组件库,如果说连 vue 都没,那这个项目没有意义
bundledDependencies: 捆绑依赖
optionalDependencies: 可选依赖
npm、cnpm、yarn、pnmp、npx
npm: 包管理器,方便开发者分享和下载开源包。经历了许多重大版本的更新,各方面已经和 yarn 在同一水平
npx: npm@5.2的产物,方便运行本地命令
npx 会帮你执行依赖包里的二进制文件: 不需要在 scripts 中声明命令
npx 原理: 运行的时候,会到 node_modules/.bin 路径和环境变量$PATH里面,检查命令是否存在。由于 npx 会检查环境变量$PATH,所以系统命令也可以调用。
避免全局安装模块: npx 将 create-react-app 下载到一个临时目录,使用以后再删除。所以,以后再次执行上面的命令,会重新下载 create-react-app。
—no-install: 如果想让 npx 强制使用本地模块,不下载远程模块,可以使用—no-install 参数。如果本地不存在该模块,就会报错。
—ignore-existing: 如果忽略本地的同名模块,强制安装使用远程模块,可以使用—ignore-existing 参数。
使用不同版本的 node: $ npx node@0.12.8 -v,原理是从 npm 下载这个版本的 node,使用后再删掉。某些场景下,这个方法用来切换 Node 版本,要比 nvm 那样的版本管理器方便一些。
-p: 用于指定 npx 所要安装的模块。$ npx -p node@0.12.8 node -v,先指定安装node@0.12.8,然后再执行 node -v 命令。
-c: 如果 npx 安装多个模块,默认情况下,所执行的命令之中,只有第一个可执行项会使用 npx 安装的模块,后面的可执行项还是会交给 Shell 解释。-c 参数可以将所有命令都用 npx 解释
cnpm: 方便中国开发者下载依赖包而诞生的下载器
yarn: 解决了 npm@5 之前的一些让人诟病的问题,同时拥有一些其它的优点。例如离线安装、失败自动重试安装和并行下载等
pnpm: 通过连接的方式,让多个项目的依赖公用同一个包,大大节省了磁盘空间
pnpm 运行起来非常的快,超过了 npm 和 yarn
pnpm 采用了一种巧妙的方法,利用硬链接和符号链接来避免复制所有本地缓存源文件, yarn 的最大的性能弱点之一
使用链接并不容易,会带来一堆问题需要考虑
pnpm 继承了 yarn 的所有优点,包括离线模式和确定性安装
node API Buffer
Buffer 是一种计算机中数据流结构。计算机中是以二进制的方式,进行数据存取的。而 js 在一开始,没有文件读写能力的,就要借助 Buffer 来实现一些缓冲区的内容。 Buffer 一般用于表示固定长度的缓冲区序列。浏览器中使用 File new Blob。
Buffer 的声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let buf1 = Buffer .alloc (5 ); let buf2 = Buffer .from ("麓一" ); let buf3 = Buffer .from ([0xe9 , 0xba , 0x93 ]);console .log (buf1); console .log (buf2); console .log (buf3.toString ()); buf2.copy (buf1, 0 , 0 , 2 ); let bigBuffer = Buffer .concat ([buf1, buf2], 6 );buf1.slice (0 , 6 ); Buffer .isBuffer (buf);
Stream
防止淹没可用内存: Buffer 不适合大文件的读取,适合比较小的文件,对于大文件,需要使用流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 const fs = require ("fs" );const path = require ("path" );const res = fs.createReadStream (path.resolve (__dirname, "../package.json" ), { flags : "r" , start : 0 , end : 20 , highWaterMark : 5 , autoClose : true , emitClose : true , }); let arr = [];res.on ("open" , function (fd ) { console .log ("fd" , fd); }); res.on ("data" , function (data ) { console .log ("data" , data); arr.push (data); }); res.on ("end" , function ( ) { console .log ("end" , Buffer .concat (arr).toString ()); }); res.on ("close" , function ( ) { console .log ("close" ); }); res.on ("error" , function ( ) { console .log ("error" ); });
cluster 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const fs = require ("fs" );const path = require ("path" );const http = require ("http" );const os = require ("os" );const { default : cluster } = require ("cluster" );const cpu_num = os.cpus ();if (cluster.isMaster ) { for (let i = 0 ; i < cpu_num.length ; i++) { cluster.fork (); } } else { http .createServer ((req, res ) => { res.end ("childPid" , process.pid ); }) .listen (3000 ); }
事件循环 浏览器的事件循环
messageBump: 宏任务 -> 微任务 -> RAF -> Layout -> RequestIdleCallback
Node 的事件循环
基于 Libuv。Libuv 是一个高性能的、事件驱动的 I/O 库,为 Node.js 提供了跨平台的异步 I/O 能力,使之能够高效地处理大量并发请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 同步的代码 | process.nextTick / promise... | ┌───────────────────────────┐ ┌─>│ timers │ 定时器: setTimeout / setInterval │ └─────────────┬─────────────┘ | process.nextTick / promise... │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ 执行延迟到下一个循环迭代的I/O回调 │ └─────────────┬─────────────┘ | process.nextTick / promise... │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ 系统内部使用 │ └─────────────┬─────────────┘ ┌───────────────┐ | process.nextTick / promise... │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ | process.nextTick / promise... │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ setImmediate │ └─────────────┬─────────────┘ | process.nextTick / promise... │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ 关闭回调函数 └───────────────────────────┘
定时器: 本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
待定回调: 执行延迟到下一个循环迭代的 I/O 回调。
idle, prepare: 仅系统内部使用。
轮询: 检索新的 I/O 事件; 执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
检测: setImmediate() 回调函数在这里执行。
关闭的回调函数: 一些关闭的回调函数,如: socket.on(‘close’, …)。
总结Node.js: microtask 在事件循环的各个阶段之间执行
> 浏览器: microtask 在事件循环的 macrotask 执行完之后执行
node 的初始化
初始化 node 环境
执行输入代码
执行 process.nextTick 回调
执行微任务队列
进入 event-loop
进入 timers 阶段(执行 setTimeout 和 SetInterval)
检查 timer 队列是否有到期的 timer 回调,如果有,将到期的 timer 回调按照 timerid 升序进行
检查是否有 process.nextTick 任务,如果有全部执行
检查是否有 microtask,有全部执行
退出该阶段
进入 IO 阶段
检查是否有 pending 的 io 回调,如果有,执行回调,如果没有,退出该阶段
检查是否有 process.nextTick()任务,如果有,全部执行
检查是否有 MicroTask,如果有全部执行
退出该阶段
进入 idle,prepare 阶段
进入 poll 阶段
首先检查是否存在尚未完成的回调,存在,则
如果 有可用回调,
那么执行
检查是否有 process.nextTick()回调,有全部执行
检查是否有 MicroTask,如果有全部执行
退出该阶段
如果没有可用回调
检查是否有 immediate 回调,如果有退出 poll 阶段,如果没有,阻塞在此阶段,等待新的事件通知
如果不存在尚未完成的回调,退出 poll 阶段
进入 check 阶段
如果有 immediate 回调,执行所有回调
检查是否有 process.nextTick()回调,如果有,全部执行
检查是否有 MicroTask,如果有全部执行
退出该阶段
进入 closing 阶段
如果有 immediate 回调,则执行所有 immediate 回调。
检查是否有 process.nextTick 回调,如果有,全部执行。
检查是否有 microtaks,如果有,全部执行。
退出 closing 阶段
检查是否有活跃的 handles(定时器、io 等事件)
如果有,继续下一轮循环
没有则结束事件循环,退出程序
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 async function async1 ( ) { console .log ("async1 started" ); await async2 (); console .log ("async end" ); } async function async2 ( ) { console .log ("async2" ); } console .log ("script start." );setTimeout (() => { console .log ("setTimeout0" ); setTimeout (() => { console .log ("setTimeout1" ); }, 0 ); setImmediate (() => { console .log ("setImmediate" ); }); }, 0 ); async1 ();process.nextTick (() => { console .log ("nextTick" ); }); new Promise ((resolve ) => { console .log ("promise1" ); resolve (); console .log ("promise2" ); }).then (() => { console .log ("promise.then" ); }); console .log ("script end." );
Node 框架 express / koa
express 是一个基于 node.js 平台的一个灵活的 web 应用开发框架,connect 中间件,内置了视图、static 等部分 koa2 相对来说更新一些,也是由 express 原班人马打造的框架,通过中间件来实现
1 2 3 4 5 6 7 8 const express = require ("express" );const path = require ("path" );const app = express ();app.use (express.static (path.resolve (__dirname, "./public" ))); app.listen (3000 , () => console .log ("server is running in 3000" ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 const Koa = require ("koa" );const app = new Koa ();const api = ( ) => new Promise ((resolve ) => { setTimeout (() => { console .log ("timing..." ); resolve (100 ); }, 100 ); }); app.use (async (ctx, next) => { console .log ("querying start 1" ); const result = await api (); ctx.result = result; await next (); console .log ("querying end 1" ); }); app.use (async (ctx, next) => { console .log ("querying start 2" , ctx.result ); next (); console .log ("querying end 2" ); }); app.use (async (ctx, next) => { console .log ("querying start 3" ); next (); console .log ("querying end 3" ); }); const main = (ctx ) => { ctx.body = "hello world" ; }; app.use (main); app.listen (3008 );
洋葱模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 function num (ctx, next ) { console .log ("starting num ..." ); next (ctx * 10 ); console .log ("ending num ..." ); } function discount (ctx, next ) { console .log ("starting discount ..." ); next (ctx * 0.8 ); console .log ("ending discount ..." ); } function express (ctx, next ) { console .log ("starting express ..." ); next (ctx + 12 ); console .log ("ending express ..." ); } function compose (args ) { let result; return function (ctx ) { let i = 0 ; let dispatch = function (i, ctx ) { let fn; if (i < args.length ) fn = args[i]; if (i === args.length ) { result = ctx; return ; } return fn (ctx, dispatch.bind (null , ++i)); }; dispatch (i, ctx); return result; }; } const sell = compose ([num, discount, express]);console .log (sell (150 ));
koa 框架原理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 listen (...args ) { debug ('listen' ); const server = http.createServer (this .callback ()); return server.listen (...args); } callback ( ) { const fn = compose (this .middleware ); if (!this .listenerCount ('error' )) this .on ('error' , this .onerror ); const handleRequest = (req, res ) => { const ctx = this .createContext (req, res); return this .handleRequest (ctx, fn); }; return handleRequest; } handleRequest (ctx, fnMiddleware ) { const res = ctx.res ; res.statusCode = 404 ; const onerror = err => ctx.onerror (err); const handleResponse = ( ) => respond (ctx); onFinished (res, onerror); return fnMiddleware (ctx).then (handleResponse).catch (onerror); } use (fn ) { ... this .middleware .push (fn); return this ; } createContext (req, res ) { const context = Object .create (this .context ); const request = context.request = Object .create (this .request ); const response = context.response = Object .create (this .response ); context.app = request.app = response.app = this ; context.req = request.req = response.req = req; context.res = request.res = response.res = res; request.ctx = response.ctx = context; request.response = response; response.request = request; context.originalUrl = request.originalUrl = req.url ; context.state = {}; return context; }
koa-compose 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 "use strict" ;module .exports = compose;function compose (middleware ) { if (!Array .isArray (middleware)) throw new TypeError ("Middleware stack must be an array!" ); for (const fn of middleware) { if (typeof fn !== "function" ) throw new TypeError ("Middleware must be composed of functions!" ); } return function (context, next ) { let index = -1 ; return dispatch (0 ); function dispatch (i ) { if (i <= index) return Promise .reject (new Error ("next() called multiple times" )); index = i; let fn = middleware[i]; if (i === middleware.length ) fn = next; if (!fn) return Promise .resolve (); try { return Promise .resolve (fn (context, dispatch.bind (null , i + 1 ))); } catch (err) { return Promise .reject (err); } } }; }
koa 常用中间件
koa2-cors
koa-static
koa-bodyparser
BFF
Backends For Frontends
在后端普遍采用微服务的情况下,作为适配层,更好的为前端服务。 优势: 降低沟通成本,提升用户体验 问题: 资源浪费,增加架构复杂度,管理复杂
Sequelize
Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能