特性:
计算事件的两种类型:CPU密集和I/O密集
进程
计算机中的程序,是系统进行资源分配和调度的基本单位。
多进程:启动多个进程,多个进程可以一块执行多个任务
线程:
进程内一个相对独立的、可调度的执行单元,与同属一个进程的线程共享进程资源。
多线程:启动一个进程,在一个进程内启动多个线程,多个线程可以一块执行多个任务
node工作模式
node适合场景
- web场景
- web server
- 本地代码构建
- 实用工具开发
环境
- CommonJs规范
- global全局对象
- process
在node环境中全局对象为global,相当于浏览器下的window对象。
另外,全局对象下的this=== module.exports。
用node执行下面语句的文件
console.log(this=== module.exports); // true
如果要查看全局对象下this可以使用自执行函数
(function(){console.log(Object.keys(this))})();/* ['global','clearInterval','clearTimeout','setInterval','setTimeout','queueMicrotask','clearImmediate','setImmediate']*/
require特性:
- nvm:nodejs 版本管理工具。
也就是说:一个 nvm 可以管理很多 node 版本和 npm 版本。 - nodejs:在项目开发时的所需要的代码库
npm:nodejs 包管理工具。
在安装的 nodejs 的时候,npm 也会跟着一起安装,它是包管理工具。
npm 管理 nodejs 中的第三方插件nvm、nodejs、npm的关系:
nvm 管理 nodejs 和 npm 的版本。npm 可以管理 nodejs 的第三方插件。
安装 nvm
安装命令:
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
安装完成后关闭终端,重新打开终端输入 nvm 验证一下是否安装成功,当出现“
Node Version Manager”时,说明已安装成功
如果在新的终端输入 nvm 时提示:command not found: nvm,有可能是以下原因之一:你的系统可能缺少一个 .bashprofile 文件,你可以创建一个此文件(可通过
vi或vim命令),打开复制粘贴以下代码(安装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文件路径配置到环境变量中
常用API
process对象,系统参数
// 判断系统console.log(process.platform) // window => "win32" mac=>"darwin"console.log(process.argv.slice(2)) // 代表用户传递的参数 默认是前两个参数 第一个node的路径,第二个是执行文件的路径地址
process.argv属性的参数
process.argv 返回一个数组,数组元素分别如下:
- 元素1:node
- 元素2:可执行文件的绝对路径
- 元素x:其他,比如参数等
运行命令 NODE_ENV=dev node argv.js —env production,输出如下。(不包含环境变量)// print process.argvprocess.argv.forEach(function(val, index, array) {console.log('参数' + index + ': ' + val);});
参数0: /Users/a/.nvm/versions/node/v6.1.0/bin/node参数1: /Users/a/Documents/git-code/nodejs-learning-guide/examples/2016.11.22-node-process/argv.js参数2: --env参数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
const {argv} = require('process');argv.forEach( function(element, index) {console.log(index+'-'+element);});
process.execArgv属性
获取node程序的特有参数,这部分参数不会出现在argv中。
如:—harmony【翻译:和谐】,主要是加上该参数可以执行v8的一些staged的功能,还不太稳定
process.execArgv.forEach((val, index, array)=>{console.log('execArgv'+index + ':' + val)})process.argv.forEach(function(val, index, array) {console.log('argv'+index + ': ' + val);});
//执行命令node --harmony execargv.js --name shuai// execArgv0:--harmony// argv0: /root/.nvm/versions/node/v16.11.0/bin/node// argv1: /data/testProcess/execargv.js// argv2: --name// argv3: shuai
process.env属性
env属性可以通过NODE_ENV传入, 在node命令前注入的环境变量。argv是node 命令后,设置的参数。
// 执行node命令NODE_ENV=dev node processArgv.js --env prod// processArgv.jsconsole.log(process.env.NODE_ENV)
常用commander库
// commander 处理命令行操作const com = require("commander");// 解析用户传入的参数,配置属性,解析命令中的静态参数// node .\commander.js -p 3000 -v 1.0.1com.option("-p, --port <val>", "set port").option("-v, --vsion <val>", "set vsion").version("1.2.3");// console.log(com._optionValues.port, com._optionValues.vsion);// 执行动作, 配置命令,输入命令后执行一些动作com.command("create ").alias(" c").description("Create project").action(() => {console.log(`git clone ${com.args[1]} project...`);});com.on("--help", () => {console.log("\r\n Example");console.log(" node .commander.js create projectName");console.log(" node .commander.js --help");}).parse(process.argv);// console.log(com.args[1]);
执行测试
- node .\commander.js -p 3000 -v 1.0.1
- node .\commander.js create xxpro
- node .\commander.js —help
path:处理路径相关
const {normalize, join, resolve} = require('path')// normalize 规范格式化路径console.log(normalize("/user//list/xiaoming")); // /user/list/xiaomingconsole.log(normalize("/user/../list/xiaoming")); // /list/xiaoming// join拼接路径console.log(join("user", "..", "list", "li")); // list\li// resolve把相对路径转为绝对路径console.log(resolve("./")); //d:\web\node\startconst {basename, extname, dirname} = require('path')// basename:文件名,extname:文件后缀名,dirname:文件夹所在的目录名console.log(dirname("D://code/node/user.js")); // D://code/nodeconsole.log(basename("D://code/node/user.js")); // user.jsconsole.log(extname("D://code/node/user.js")); //.jsconst {parse, format} = require('path')// parse将路径格式化路径对象console.log(parse("D:/code/node/node_modules/package.json"));/**{root: 'D:/',dir: 'D:/code/node/node_modules',base: 'package.json',ext: '.json',name: 'package'} **/const {sep, delimiter, win32, posix} = reuire('path')
path的relative方法
根据当前工作目录返回 from 到 to 的相对路径。
path.relative('C:\\orandea\\test\\aaa', 'C:\\orandea\\impl\\bbb');// 输出 ..\..\impl\bbbpath.relative('C:\\orandea\\test', 'C:\\orandea\\impl\\bbb');// 输出 ..\impl\bbb
系统路径dirname和filename
dirname、filename返回文件夹或文件的绝对路径,路径地址不会变
process.cwd()返回node命令执行所在的文件夹,会随着node执行命令的路径变化
console.log("__dirname", __dirname); // 返回文件夹的绝对路径console.log("__filename", __filename);console.log("process.cwd()", process.cwd());
./ 引用文件的地址
- 在require方法中,表示相对当前文件所在的文件夹
在其他地方使用,和process.cwd()一样,相对node启动执行的文件夹
fs:处理文件相关
文件I/O操作,require(‘fs’),回调函数的第一个参数都是err异常。
读文件readFile、readFileSync
const fs = require('fs')fs.readFile('./1.txt', 'utf8', (err,data)=>{if(err) throw err;console.log(data)})//同步读取文件const res = fs.readFileSync('./1.txt')
写文件writeFile
const fs = require('fs')fs.writeFile('./2.txt', "this is text", 'utf8', (err)=>{if(err)throw errconsole.log("done")})
stat和文件信息相关
const fs = require('fs')fs.stat('/a.js', (err,stats)=>{if(err){console.log('文件不存在')return}console.log(stats.isFile()) //是否是文件console.log(stats.isDirectory()) //是否是文件夹console.log(stats)})
rename重命名
const fs = require('fs')fs.rename('/a.js', 'b.js', (err,stats)=>{if(err)throw errconsole.log("done")})
unlink删除文件
const fs = require('fs')fs.unlink('/a.js', (err)=>{if(err)throw err})
readdir读取文件夹
fs.readdir("./", (err,files)=>{if(err) throw errconsole.log(files)})
mkdir创建文件夹
fs.mkdir("code", (err)=>{if(!err) console.log("done")})
rmdirSync同步删除文件夹
fs.rmdirSync('./code',{recursive: true, //表示递归删除内部文件夹}, err=>{if(!err) console.log('done')})
watch监视文件的变化
fs.watch('./', {recursive: true, //表示递归删除内部文件夹}, (eventType,filename)=>{console.log(eventType,filename)})
获取指定路径下的文件夹
```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()) {
result.push(item) // 存储当前文件夹的名字
} })
return result }
module.exports = { getAllFolderName }
<a name="N61mI"></a>### Buffer二进制文件特性1. Buffer用于处理二进制数据流2. 实例类似整数数组,大小固定3. C++ 代码在V8堆外分配物理地址4. Buffer是全局对象,在node中直接使用```javascriptconsole.log(Buffer.alloc(10));console.log(Buffer.alloc(10, 1));console.log(Buffer.from([1, 2, 3]));
Buffer.byteLength
Buffer.isBuffer()
Buffer.concat()
console.log(Buffer.byteLength("abc"));console.log(Buffer.isBuffer(Buffer.from([1, 2, 3])));
let buf = Buffer.from("abc")console.log(buf.length)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观察者
事件循环操作顺序
┌───────────────────────────┐┌─>│ timers ││ └─────────────┬─────────────┘│ ┌─────────────┴─────────────┐│ │ pending callbacks ││ └─────────────┬─────────────┘│ ┌─────────────┴─────────────┐│ │ idle, prepare ││ └─────────────┬─────────────┘ ┌───────────────┐│ ┌─────────────┴─────────────┐ │ incoming: ││ │ poll │<─────┤ connections, ││ └─────────────┬─────────────┘ │ data, etc. ││ ┌─────────────┴─────────────┐ └───────────────┘│ │ check ││ └─────────────┬─────────────┘│ ┌─────────────┴─────────────┐└──┤ close callbacks │└───────────────────────────┘

上图来源:https://developer.ibm.com/tutorials/learn-nodejs-the-event-loop/
setImmediate和setTimeout对比
setImmediate() 和 setTimeout() 很类似,但是基于被调用的时机,他们也有不同表现。
- setImmediate() 设计为一旦在当前 轮询 阶段完成, 就执行脚本。
- setTimeout() 在最小阈值(ms 单位)过后运行脚本。
执行计时器的顺序将根据调用它们的上下文而异。如果二者都从主模块内调用,则计时器将受进程性能的约束(这可能会受到计算机上其他正在运行应用程序的影响)。
例如,如果运行以下不在 I/O 周期(即主模块)内的脚本,则执行两个计时器的顺序是非确定性的,因为它受进程性能的约束:
setTimeout(() => {console.log('timeout');}, 0);setImmediate(() => {console.log('immediate');});// timeout// immediate
如果你把这两个函数放入一个 I/O 循环内调用,setImmediate 总是被优先调用:
const fs = require('fs');fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');}, 0);setImmediate(() => {console.log('immediate');});});// immediate// timeout
process.Tick和setImmediate对比
- process.nextTick() 在同一个阶段立即执行。
- setImmediate() 在事件循环的接下来的迭代或 ‘tick’ 上触发。
process.nextTick() 比 setImmediate() 触发得更快,但这是过去遗留问题,因此不太可能改变。
为什么要使用 process.nextTick()?
有两个主要原因:
- 允许用户处理错误,清理任何不需要的资源,或者在事件循环继续之前重试请求。
- 有时有让回调在栈展开后,但在事件循环继续之前运行的必要。

console.log("主线程同步结束执行");process.nextTick(() => {console.log("nextTick 11111");});process.nextTick(() => {console.log("nextTick 22");});setImmediate(() => {console.log("setImmediate111");process.nextTick(() => {console.log("setImmediate111 then 0000");});});process.nextTick(() => {console.log("nextTick 333");});setImmediate(() => {console.log("setImmediate333");});// 输出结果主线程同步结束执行nextTick 11111nextTick 22nextTick 333setImmediate111setImmediate111 then 0000setImmediate333
node事件循环,process.nextTick早于promise.resolve().then()
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
归纳总结一下:就是JavaScript有一个主线程和一个异步任务队列线程
setTimeout(function(){console.log("setTimeout执行");});setImmediate(function () {console.log("setImmediate执行");});process.nextTick(function(){console.log("process.nextTick执行");});Promise.resolve().then(function(){console.log("Promise执行");});console.log("同步执行");process.nextTick(function(){console.log("process.nextTick执行2");});// 代码输出同步执行process.nextTick执行process.nextTick执行2Promise执行setTimeout执行setImmediate执行
当前执行栈
- process.nextTick()
NodeJS新增的api,表示:在当前同步任务”执行栈”的尾部——下一次Event Loop(主线程读取”任务队列”)之前,会早于Promise,触发回调函数 - Promise()
在当前同步任务执行栈的尾部执行
从任务队列取出来执行
- setTimeout()
延时时间到,并添加到当前”任务队列”的尾部,在下一次Event Loop时执行时从“任务队列”取出来执行。setInterval()和setTimeout()原理一样。 - setImmediate()
在当前”任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行
events事件
const EventEmitter = require('events')class MyEvent extends EventEmitter{}const myEvent = new MyEvent()myEvent.on("log",()=>console.log("触发了console.log事件"))myEvent.emit('event')
监听和移除事件
const EventEmitter = require("events");class MyEvent extends EventEmitter {}const myEvent = new MyEvent();const fn1 = () => console.log("111");const fn2 = () => console.log("222");myEvent.on("log", fn1);myEvent.on("log", fn2);setInterval(() => {myEvent.emit("log");}, 500);setTimeout(() => {// 移除一个事件// myEvent.removeListener("log", fn2);// 移除所有事件myEvent.removeAllListeners("log");}, 1600);
stream流
createReadStream读文件
const fs = require('fs')const rs = fs.createReadStream('./1.txt')rs.pipe(process.stdout) // 输出数据,打印到控制台
createWriteStream写数据
const fs = require('fs')const ws = fs.createWriteSteam('./1.txt')
promisify处理异步
const {promisify} = require("util")const read = promisify(fs.readFile)async function getFile(){try{const content = await read('./1.txt')console.log(content.toString())}catch(err){console.err(err)}}
静态服务器
依赖http模块,服务器
const http = require("http")let server = http.createServer((req,res)=>{res.status = 200res.setHeader("Content-Type", "text/plain")res.end("hello node server")})server.listen(8000, "localhost", ()=>{console.log("server running on 8000")})
静态http服务器读取文件和文件夹
const http = require("http")const fs = require('fs')let server = http.createServer((req,res)=>{fs.stat(filePath, (_err, stats) => {try {if (stats.isFile()) {res.statusCode = 200;res.setHeader("Content-Type", "text/plain; charset=utf-8");// 将读取的文件内容输出到页面fs.createReadStream(filePath).pipe(res);return;} else if (stats.isDirectory()) {// 如果是文件夹,将内部的文件名读取出来,用逗号拼接成字符串fs.readdir(filePath, (err, files) => {res.statusCode = 200;res.setHeader("Content-Type", "text/plain; charset=utf-8");res.end(files.join(","));return;});}}catch (error) {res.statusCode = 404;res.setHeader("Content-Type", "text/plain;charset=utf-8");res.end("访问的路径不存在");}});res.status = 200res.setHeader("Content-Type", "text/plain")res.end("hello node server")})server.listen(8000, "localhost", ()=>{console.log("server running on 8000")})
Content-Type设置文件类型
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(服务器端响应)(只要文件一改变,就发生状态改变)
