特性:
计算事件的两种类型: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.argv
process.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.js
console.log(process.env.NODE_ENV)
常用commander库
// commander 处理命令行操作
const com = require("commander");
// 解析用户传入的参数,配置属性,解析命令中的静态参数
// node .\commander.js -p 3000 -v 1.0.1
com
.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/xiaoming
console.log(normalize("/user/../list/xiaoming")); // /list/xiaoming
// join拼接路径
console.log(join("user", "..", "list", "li")); // list\li
// resolve把相对路径转为绝对路径
console.log(resolve("./")); //d:\web\node\start
const {basename, extname, dirname} = require('path')
// basename:文件名,extname:文件后缀名,dirname:文件夹所在的目录名
console.log(dirname("D://code/node/user.js")); // D://code/node
console.log(basename("D://code/node/user.js")); // user.js
console.log(extname("D://code/node/user.js")); //.js
const {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\bbb
path.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 err
console.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 err
console.log("done")
})
unlink删除文件
const fs = require('fs')
fs.unlink('/a.js', (err)=>{
if(err)throw err
})
readdir读取文件夹
fs.readdir("./", (err,files)=>{
if(err) throw err
console.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中直接使用
```javascript
console.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 11111
nextTick 22
nextTick 333
setImmediate111
setImmediate111 then 0000
setImmediate333
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执行2
Promise执行
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 = 200
res.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 = 200
res.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(服务器端响应)(只要文件一改变,就发生状态改变)