特性:

node的特性:异步、事件驱动、非阻塞I/O。

计算事件的两种类型:CPU密集和I/O密集

  • CPU密集:压缩、解压、加密、解密、计算
  • I/O密集:文件操作、网络操作、数据库

    处理高并发

  • 增加服务器机器数

  • 增加每台服务器的CPU数

进程

计算机中的程序,是系统进行资源分配和调度的基本单位。
多进程:启动多个进程,多个进程可以一块执行多个任务

线程:

进程内一个相对独立的、可调度的执行单元,与同属一个进程的线程共享进程资源。
多线程:启动一个进程,在一个进程内启动多个线程,多个线程可以一块执行多个任务

node工作模式

node.png

node适合场景

  • web场景
  • web server
  • 本地代码构建
  • 实用工具开发

环境

  1. CommonJs规范
  2. global全局对象
  3. process

在node环境中全局对象为global,相当于浏览器下的window对象。
另外,全局对象下的this=== module.exports。
用node执行下面语句的文件

  1. console.log(this=== module.exports); // true

如果要查看全局对象下this可以使用自执行函数

  1. (function(){
  2. console.log(Object.keys(this))
  3. })();
  4. /* [
  5. 'global',
  6. 'clearInterval',
  7. 'clearTimeout',
  8. 'setInterval',
  9. 'setTimeout',
  10. 'queueMicrotask',
  11. 'clearImmediate',
  12. 'setImmediate'
  13. ]
  14. */

require特性:

  1. module被加载的时候执行,加载后缓存
  2. 一旦出现某个模块被循环加载,就只输出已执行的部分,还未执行的部分不会输出

    使用nvm安装node

  • nvm:nodejs 版本管理工具。
    也就是说:一个 nvm 可以管理很多 node 版本和 npm 版本。
  • nodejs:在项目开发时的所需要的代码库
  • npm:nodejs 包管理工具。
    在安装的 nodejs 的时候,npm 也会跟着一起安装,它是包管理工具。
    npm 管理 nodejs 中的第三方插件

    nvm、nodejs、npm的关系:

    nvm 管理 nodejs 和 npm 的版本。npm 可以管理 nodejs 的第三方插件。

    安装 nvm

    安装命令:
    1. curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash

    安装完成后关闭终端,重新打开终端输入 nvm 验证一下是否安装成功,当出现“Node Version Manager”时,说明已安装成功
    如果在新的终端输入 nvm 时提示:command not found: nvm,有可能是以下原因之一:

  • 你的系统可能缺少一个 .bashprofile 文件,你可以创建一个此文件(可通过vivim命令),打开复制粘贴以下代码(安装nvm成功后终端的最好3行代码_)进去,保存,然后再次运行安装命令;

nvm 相关操作

nvm ls-remote 列出远程服务器上的所有版本
nvm list 或 $ nvm ls 查看已安装的版本
nvm current 查看当前的版本
nvm alias default 指定一个默认的版本
nvm install 安装指定的版本
nvm use 切换不同的版本
nvm uninstall 删除指定的版本

全局安装命令后,使用时报错

安装node后,更改了node-global文件的路径地址。再次全局安装一些命令时,例如npm i -g anyopenserver
调用anyopen命令时报错
解决方法:
将更新后的node-global文件路径配置到环境变量中
Snipaste_2020-09-26_22-09-58.png

常用API

process对象,系统参数

  1. // 判断系统
  2. console.log(process.platform) // window => "win32" mac=>"darwin"
  3. console.log(process.argv.slice(2)) // 代表用户传递的参数 默认是前两个参数 第一个node的路径,第二个是执行文件的路径地址

process.argv属性的参数

process.argv 返回一个数组,数组元素分别如下:

  • 元素1:node
  • 元素2:可执行文件的绝对路径
  • 元素x:其他,比如参数等
    1. // print process.argv
    2. process.argv.forEach(function(val, index, array) {
    3. console.log('参数' + index + ': ' + val);
    4. });
    运行命令 NODE_ENV=dev node argv.js —env production,输出如下。(不包含环境变量)
    1. 参数0: /Users/a/.nvm/versions/node/v6.1.0/bin/node
    2. 参数1: /Users/a/Documents/git-code/nodejs-learning-guide/examples/2016.11.22-node-process/argv.js
    3. 参数2: --env
    4. 参数3: production

新建process-argv.js,使用

$node process-args.js one —two three=3 four

执行结果
0-H:\Program Files\nodejs\node.exe
1-G:\web\node\node-base\process\process-args.js
2-one
3—-two
4-three=3
5-four

  1. const {argv} = require('process');
  2. argv.forEach( function(element, index) {
  3. console.log(index+'-'+element);
  4. });

process.execArgv属性

获取node程序的特有参数,这部分参数不会出现在argv中。
如:—harmony【翻译:和谐】,主要是加上该参数可以执行v8的一些staged的功能,还不太稳定

  1. process.execArgv.forEach((val, index, array)=>{
  2. console.log('execArgv'+index + ':' + val)
  3. })
  4. process.argv.forEach(function(val, index, array) {
  5. console.log('argv'+index + ': ' + val);
  6. });
  1. //执行命令
  2. node --harmony execargv.js --name shuai
  3. // execArgv0:--harmony
  4. // argv0: /root/.nvm/versions/node/v16.11.0/bin/node
  5. // argv1: /data/testProcess/execargv.js
  6. // argv2: --name
  7. // argv3: shuai

process.env属性

env属性可以通过NODE_ENV传入, 在node命令前注入的环境变量。argv是node 命令后,设置的参数。

  1. // 执行node命令
  2. NODE_ENV=dev node processArgv.js --env prod
  3. // processArgv.js
  4. console.log(process.env.NODE_ENV)

常用commander库

  1. // commander 处理命令行操作
  2. const com = require("commander");
  3. // 解析用户传入的参数,配置属性,解析命令中的静态参数
  4. // node .\commander.js -p 3000 -v 1.0.1
  5. com
  6. .option("-p, --port <val>", "set port")
  7. .option("-v, --vsion <val>", "set vsion")
  8. .version("1.2.3");
  9. // console.log(com._optionValues.port, com._optionValues.vsion);
  10. // 执行动作, 配置命令,输入命令后执行一些动作
  11. com
  12. .command("create ")
  13. .alias(" c")
  14. .description("Create project")
  15. .action(() => {
  16. console.log(`git clone ${com.args[1]} project...`);
  17. });
  18. com
  19. .on("--help", () => {
  20. console.log("\r\n Example");
  21. console.log(" node .commander.js create projectName");
  22. console.log(" node .commander.js --help");
  23. })
  24. .parse(process.argv);
  25. // console.log(com.args[1]);

执行测试

  1. node .\commander.js -p 3000 -v 1.0.1
  2. node .\commander.js create xxpro
  3. node .\commander.js —help

path:处理路径相关

  1. const {normalize, join, resolve} = require('path')
  2. // normalize 规范格式化路径
  3. console.log(normalize("/user//list/xiaoming")); // /user/list/xiaoming
  4. console.log(normalize("/user/../list/xiaoming")); // /list/xiaoming
  5. // join拼接路径
  6. console.log(join("user", "..", "list", "li")); // list\li
  7. // resolve把相对路径转为绝对路径
  8. console.log(resolve("./")); //d:\web\node\start
  9. const {basename, extname, dirname} = require('path')
  10. // basename:文件名,extname:文件后缀名,dirname:文件夹所在的目录名
  11. console.log(dirname("D://code/node/user.js")); // D://code/node
  12. console.log(basename("D://code/node/user.js")); // user.js
  13. console.log(extname("D://code/node/user.js")); //.js
  14. const {parse, format} = require('path')
  15. // parse将路径格式化路径对象
  16. console.log(parse("D:/code/node/node_modules/package.json"));
  17. /**
  18. {
  19. root: 'D:/',
  20. dir: 'D:/code/node/node_modules',
  21. base: 'package.json',
  22. ext: '.json',
  23. name: 'package'
  24. } **/
  25. const {sep, delimiter, win32, posix} = reuire('path')

path的relative方法

根据当前工作目录返回 fromto 的相对路径。

  1. path.relative('C:\\orandea\\test\\aaa', 'C:\\orandea\\impl\\bbb');
  2. // 输出 ..\..\impl\bbb
  3. path.relative('C:\\orandea\\test', 'C:\\orandea\\impl\\bbb');
  4. // 输出 ..\impl\bbb

类似于命令行切换文件路径的命令

系统路径dirname和filename

dirname、filename返回文件夹或文件的绝对路径,路径地址不会变
process.cwd()返回node命令执行所在的文件夹,会随着node执行命令的路径变化

  1. console.log("__dirname", __dirname); // 返回文件夹的绝对路径
  2. console.log("__filename", __filename);
  3. console.log("process.cwd()", process.cwd());

./ 引用文件的地址

  • 在require方法中,表示相对当前文件所在的文件夹
  • 在其他地方使用,和process.cwd()一样,相对node启动执行的文件夹

    fs:处理文件相关

    文件I/O操作,require(‘fs’),回调函数的第一个参数都是err异常。

    读文件readFile、readFileSync

    1. const fs = require('fs')
    2. fs.readFile('./1.txt', 'utf8', (err,data)=>{
    3. if(err) throw err;
    4. console.log(data)
    5. })
    6. //同步读取文件
    7. const res = fs.readFileSync('./1.txt')

    写文件writeFile

    1. const fs = require('fs')
    2. fs.writeFile('./2.txt', "this is text", 'utf8', (err)=>{
    3. if(err)throw err
    4. console.log("done")
    5. })

    stat和文件信息相关

    1. const fs = require('fs')
    2. fs.stat('/a.js', (err,stats)=>{
    3. if(err){
    4. console.log('文件不存在')
    5. return
    6. }
    7. console.log(stats.isFile()) //是否是文件
    8. console.log(stats.isDirectory()) //是否是文件夹
    9. console.log(stats)
    10. })

    rename重命名

    1. const fs = require('fs')
    2. fs.rename('/a.js', 'b.js', (err,stats)=>{
    3. if(err)throw err
    4. console.log("done")
    5. })

    unlink删除文件

    1. const fs = require('fs')
    2. fs.unlink('/a.js', (err)=>{
    3. if(err)throw err
    4. })

    readdir读取文件夹

    1. fs.readdir("./", (err,files)=>{
    2. if(err) throw err
    3. console.log(files)
    4. })

    mkdir创建文件夹

    1. fs.mkdir("code", (err)=>{
    2. if(!err) console.log("done")
    3. })

    rmdirSync同步删除文件夹

    1. fs.rmdirSync('./code',{
    2. recursive: true, //表示递归删除内部文件夹
    3. }, err=>{
    4. if(!err) console.log('done')
    5. })

    watch监视文件的变化

    1. fs.watch('./', {
    2. recursive: true, //表示递归删除内部文件夹
    3. }, (eventType,filename)=>{
    4. console.log(eventType,filename)
    5. })

    获取指定路径下的文件夹

    ```javascript const path = require(‘path’) const fs = require(‘fs’) const getAllFolderName = (mypath) => { const items = fs.readdirSync(mypath) const result = [] // 遍历当前目录中所有的文件和文件夹 items.map(item => { const temp = path.join(mypath, item) // 若当前的为文件夹 if (fs.statSync(temp).isDirectory()) {

    1. result.push(item) // 存储当前文件夹的名字

    } })

    return result }

module.exports = { getAllFolderName }

  1. <a name="N61mI"></a>
  2. ### Buffer二进制文件
  3. 特性
  4. 1. Buffer用于处理二进制数据流
  5. 2. 实例类似整数数组,大小固定
  6. 3. C++ 代码在V8堆外分配物理地址
  7. 4. Buffer是全局对象,在node中直接使用
  8. ```javascript
  9. console.log(Buffer.alloc(10));
  10. console.log(Buffer.alloc(10, 1));
  11. console.log(Buffer.from([1, 2, 3]));

Buffer.byteLength
Buffer.isBuffer()
Buffer.concat()

  1. console.log(Buffer.byteLength("abc"));
  2. console.log(Buffer.isBuffer(Buffer.from([1, 2, 3])));
  1. let buf = Buffer.from("abc")
  2. console.log(buf.length)
  3. console.log(buf.toString("base64"))

eventLoop事件循环

https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/

setTimeout采用的是类似IO观察者, setImmediate采用的是check观察者, process.nextTick()采用的是idle观察者。

三种观察者的优先级顺序是:idle观察者>>io观察者>check观察者
事件循环操作顺序

  1. ┌───────────────────────────┐
  2. ┌─>│ timers
  3. └─────────────┬─────────────┘
  4. ┌─────────────┴─────────────┐
  5. pending callbacks
  6. └─────────────┬─────────────┘
  7. ┌─────────────┴─────────────┐
  8. idle, prepare
  9. └─────────────┬─────────────┘ ┌───────────────┐
  10. ┌─────────────┴─────────────┐ incoming:
  11. poll │<─────┤ connections,
  12. └─────────────┬─────────────┘ data, etc.
  13. ┌─────────────┴─────────────┐ └───────────────┘
  14. check
  15. └─────────────┬─────────────┘
  16. ┌─────────────┴─────────────┐
  17. └──┤ close callbacks
  18. └───────────────────────────┘

image.png
上图来源:https://developer.ibm.com/tutorials/learn-nodejs-the-event-loop/

setImmediate和setTimeout对比

setImmediate() 和 setTimeout() 很类似,但是基于被调用的时机,他们也有不同表现。

  • setImmediate() 设计为一旦在当前 轮询 阶段完成, 就执行脚本。
  • setTimeout() 在最小阈值(ms 单位)过后运行脚本。

执行计时器的顺序将根据调用它们的上下文而异。如果二者都从主模块内调用,则计时器将受进程性能的约束(这可能会受到计算机上其他正在运行应用程序的影响)。
例如,如果运行以下不在 I/O 周期(即主模块)内的脚本,则执行两个计时器的顺序是非确定性的,因为它受进程性能的约束:

  1. setTimeout(() => {
  2. console.log('timeout');
  3. }, 0);
  4. setImmediate(() => {
  5. console.log('immediate');
  6. });
  7. // timeout
  8. // immediate

如果你把这两个函数放入一个 I/O 循环内调用,setImmediate 总是被优先调用:

  1. const fs = require('fs');
  2. fs.readFile(__filename, () => {
  3. setTimeout(() => {
  4. console.log('timeout');
  5. }, 0);
  6. setImmediate(() => {
  7. console.log('immediate');
  8. });
  9. });
  10. // immediate
  11. // timeout

process.Tick和setImmediate对比

  • process.nextTick() 在同一个阶段立即执行。
  • setImmediate() 在事件循环的接下来的迭代或 ‘tick’ 上触发。

process.nextTick() 比 setImmediate() 触发得更快,但这是过去遗留问题,因此不太可能改变。

为什么要使用 process.nextTick()?
有两个主要原因:

  1. 允许用户处理错误,清理任何不需要的资源,或者在事件循环继续之前重试请求。
  2. 有时有让回调在栈展开后,但在事件循环继续之前运行的必要。

node基础入门 - 图4

  1. console.log("主线程同步结束执行");
  2. process.nextTick(() => {
  3. console.log("nextTick 11111");
  4. });
  5. process.nextTick(() => {
  6. console.log("nextTick 22");
  7. });
  8. setImmediate(() => {
  9. console.log("setImmediate111");
  10. process.nextTick(() => {
  11. console.log("setImmediate111 then 0000");
  12. });
  13. });
  14. process.nextTick(() => {
  15. console.log("nextTick 333");
  16. });
  17. setImmediate(() => {
  18. console.log("setImmediate333");
  19. });
  20. // 输出结果
  21. 主线程同步结束执行
  22. nextTick 11111
  23. nextTick 22
  24. nextTick 333
  25. setImmediate111
  26. setImmediate111 then 0000
  27. setImmediate333

node事件循环,process.nextTick早于promise.resolve().then()
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
归纳总结一下:就是JavaScript有一个主线程和一个异步任务队列线程

  1. setTimeout(function()
  2. {
  3. console.log("setTimeout执行");
  4. });
  5. setImmediate(function () {
  6. console.log("setImmediate执行");
  7. });
  8. process.nextTick(function()
  9. {
  10. console.log("process.nextTick执行");
  11. });
  12. Promise.resolve().then(function()
  13. {
  14. console.log("Promise执行");
  15. });
  16. console.log("同步执行");
  17. process.nextTick(function()
  18. {
  19. console.log("process.nextTick执行2");
  20. });
  21. // 代码输出
  22. 同步执行
  23. process.nextTick执行
  24. process.nextTick执行2
  25. Promise执行
  26. setTimeout执行
  27. setImmediate执行

当前执行栈

  • process.nextTick()
    NodeJS新增的api,表示:在当前同步任务”执行栈”的尾部——下一次Event Loop(主线程读取”任务队列”)之前,会早于Promise,触发回调函数
  • Promise()
    在当前同步任务执行栈的尾部执行

从任务队列取出来执行

  • setTimeout()
    延时时间到,并添加到当前”任务队列”的尾部,在下一次Event Loop时执行时从“任务队列”取出来执行。setInterval()和setTimeout()原理一样。
  • setImmediate()
    在当前”任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行

events事件

  1. const EventEmitter = require('events')
  2. class MyEvent extends EventEmitter{}
  3. const myEvent = new MyEvent()
  4. myEvent.on("log",()=>console.log("触发了console.log事件"))
  5. myEvent.emit('event')

监听和移除事件

  1. const EventEmitter = require("events");
  2. class MyEvent extends EventEmitter {}
  3. const myEvent = new MyEvent();
  4. const fn1 = () => console.log("111");
  5. const fn2 = () => console.log("222");
  6. myEvent.on("log", fn1);
  7. myEvent.on("log", fn2);
  8. setInterval(() => {
  9. myEvent.emit("log");
  10. }, 500);
  11. setTimeout(() => {
  12. // 移除一个事件
  13. // myEvent.removeListener("log", fn2);
  14. // 移除所有事件
  15. myEvent.removeAllListeners("log");
  16. }, 1600);

stream流

createReadStream读文件

  1. const fs = require('fs')
  2. const rs = fs.createReadStream('./1.txt')
  3. rs.pipe(process.stdout) // 输出数据,打印到控制台

createWriteStream写数据

  1. const fs = require('fs')
  2. const ws = fs.createWriteSteam('./1.txt')

promisify处理异步

  1. const {promisify} = require("util")
  2. const read = promisify(fs.readFile)
  3. async function getFile(){
  4. try{
  5. const content = await read('./1.txt')
  6. console.log(content.toString())
  7. }catch(err){
  8. console.err(err)
  9. }
  10. }

静态服务器

依赖http模块,服务器

  1. const http = require("http")
  2. let server = http.createServer((req,res)=>{
  3. res.status = 200
  4. res.setHeader("Content-Type", "text/plain")
  5. res.end("hello node server")
  6. })
  7. server.listen(8000, "localhost", ()=>{
  8. console.log("server running on 8000")
  9. })

静态http服务器读取文件和文件夹

  1. const http = require("http")
  2. const fs = require('fs')
  3. let server = http.createServer((req,res)=>{
  4. fs.stat(filePath, (_err, stats) => {
  5. try {
  6. if (stats.isFile()) {
  7. res.statusCode = 200;
  8. res.setHeader("Content-Type", "text/plain; charset=utf-8");
  9. // 将读取的文件内容输出到页面
  10. fs.createReadStream(filePath).pipe(res);
  11. return;
  12. } else if (stats.isDirectory()) {
  13. // 如果是文件夹,将内部的文件名读取出来,用逗号拼接成字符串
  14. fs.readdir(filePath, (err, files) => {
  15. res.statusCode = 200;
  16. res.setHeader("Content-Type", "text/plain; charset=utf-8");
  17. res.end(files.join(","));
  18. return;
  19. });
  20. }
  21. }catch (error) {
  22. res.statusCode = 404;
  23. res.setHeader("Content-Type", "text/plain;charset=utf-8");
  24. res.end("访问的路径不存在");
  25. }
  26. });
  27. res.status = 200
  28. res.setHeader("Content-Type", "text/plain")
  29. res.end("hello node server")
  30. })
  31. server.listen(8000, "localhost", ()=>{
  32. console.log("server running on 8000")
  33. })

Content-Type设置文件类型

  1. res.setHeader("Content-Type", `text/plain; charset=utf-8`);

一般会使用一个mime.js对文件进行不同后缀的匹配。
可以安装npm install mine —save

accept/content-encoding文件压缩

header的Request中使用accept-encoding表示能接收的压缩类型
header的response中使用content-encoding表示服务器把文件压缩的方式

缓存header

  • Expires(返回的绝对时间,返回截止时间,由于时区原因误差大,比较少用。出现的比较早期。),Cache-Control (返回的相对时间,往后推迟多长时间,用的比较多)
  • If-Modified-Since(客户端请求header的修改时间) / Last-Modified(服务器端响应返回的修改时间)
  • If-None-Match(客户端请求) / ETage(服务器端响应)(只要文件一改变,就发生状态改变)