Node可以做什么
- 轻量级、高性能的web服务
- 前后端js的同构开发
- 便捷高效的前段工程化
Node的架构和运行过程
- 当前层内容由js实现
- 提供应用可直接调用库,例如fs、path、http等
- js语言无法直接操作底层硬件配置
Builtin modules 胶水层
js和具体功能实现的对照层
底层
- V8: 执行js代码,提供桥梁接口
- libuv: ⌚事件循环、 事件对列、异步IO
- 第三方模块:zlib、http、c-ares
为什么是nodejs
1.nodehs 慢慢演化为一门服务端‘语言’
2. io是计算机操作过程中最缓慢的环节
2.1 Reactor模式,单线程完成多线程的工作
假设有n个人等菜,正常的多线程就是多个服务员对应多个客人进行点菜服务
而Reactor模式,是让n个人自己点菜,最后一次性由一个人负责下单
2.2 Reactor 模式下实现异步IO、事件驱动
2.3 nodejs就是通过libuv进行异步io的处理,因此可以很好的处理io密集型高并发请求
2.4 Nodejs异步io
2.4.1 io
- 阻塞io
- 非阻塞io
- 重复调用io操作,判断io是否结束
期待实现无需主动判断的非阻塞io
libuv库通过事件驱动的形式来通知io结束,只需要在该关心的地方监听io结束事件,即可不主动调用即可知道io结束
2.4.1.2 Io总结
- io是应用程序的瓶颈所在
- 异步io提高性能无需原地等待结果返回
- io操作术语操作系统级别,平台都有对应的实现
- nodejs单线程配合事件驱动架构及libuv实现了异步io
- 异步io提高了cpu的使用率
NodeJs 事件驱动架构
1.1事件驱动架构是软件开发中的通用模式,
其主要形式可以阐述为主体发布消息,其他实例接受消息
1.2Nodejs 单线程
- 异步IO
- 事件驱动
- 事件循环
- 以上三种方式共同组合形成reator模式,单线程完成多线程的事儿
1.3 NodeJs使用Js实现高效可伸缩的高性能web服务
q: 单线程如何实现高并发
- 异步非阻塞IO配合事件回调通知
需要注意的是NodeJs主线程师单线程,而非NodeJs是单线程, 在V8中已经加入了 web workers
Nodejs 应用场景
2.1 IO密集型高并发请求
2.2 NodeJs 作为中间层
2.3 操作数据库提供api服务
const express = require('express')
const app = express()
app.listen(3000, () => {
console.log('服务启动在3000 端口')
})
复制代码
2.4 实时聊天应用程序
通过socket.io可以轻松实现实时聊天应用程序
NodeJs 全局对象
-
与浏览器平台的window不完全相同
-
NodeJs中每个模块都相当于被IIFE函数包裹,是独立的,因此不存在this指向全局对象
-
NodeJs全局对象上挂载着许多属性
1.1 NodeJs 中全局对象是global
- global 的根本作用就是作为宿主
- 全局对象可以看作是全局变量的宿主
1.2 NodeJs 常见全局变量
-
__fileName
-
返回正在执行脚本文件的绝对路径
-
__dirName
-
返回正在执行脚本文件所在目录
-
timer类函数
-
执行顺序与事件循环间的关系
-
process
-
提供与当前线程互动的接口
-
require
-
实现模块的加载
-
module\exports
-
处理模块的导出
1.3 process
- 无需require即可使用(所有的全局变量,因为nodejs在运行该脚本文件的时候就已经注入了全局变量)
- 获取进程信息
- 执行进程操作
常见方法
-
memortusage 获取资源信息内存等
{
rss: 19070976,
heapTotal: 3801088,
heapUsed: 3018384,
external: 327227,
arrayBuffers: 9914
} memoryUsage -
heapTotal 和 heapUsed 代表 V8 的内存使用情况。
-
external 代表 V8 管理的绑定到 Javascript 对象的 C++ 对象的内存使用情况。
-
rss,常驻集大小, 是为进程分配的物理内存(总分配内存的子集)的大小,包括所有的 C++ 和 JavaScript 对象与代码。
-
arrayBuffers 代表分配给 ArrayBuffer 和 SharedArrayBuffer 的内存,包括所有的 Node.js Buffer。 这也包含在 external 值中。 当 Node.js 被用作嵌入式库时,此值可能为 0,因为在这种情况下可能无法跟踪 ArrayBuffer 的分配。
-
cpuUsage
{ user: 37002, system: 24749 } cpuUsage
-
cwd 运行目录
-
version node环境
-
versions node内部uv、v8等库的版本信息集合
-
arch cpu架构
-
env.NODE_ENV 用户环境
-
env.PATH 环境变量
-
env.USERPROFILE/ env.HOME 用户根目录
-
process.platform 系统平台
-
process.argv 启动参数、pid、运行时间
-
argv0 默认传参
-
execArgv 主动传参
-
pid
-
uptime 运行时间
Path
常见api
1.1 baseName
获取路径中的基础名称
/**
* 01 返回的就是接收路径当中的最后一部分
* 02 第二个参数表示扩展名,如果说没有设置则返回完整的文件名称带后缀
* 03 第二个参数做为后缀时,如果没有在当前路径中被匹配到,那么就会忽略
* 04 处理目录路径的时候如果说,结尾处有路径分割符,则也会被忽略掉
*/
/* console.log(path.basename(__filename))
console.log(path.basename(__filename, '.js'))
console.log(path.basename(__filename, '.css'))
console.log(path.basename('/a/b/c'))
console.log(path.basename('/a/b/c/')) */
复制代码
1.2 dirname
获取路径目录名
/**
* 01 返回路径中最后一个部分的上一层目录所在路径
*/
/* console.log(path.dirname(__filename))
console.log(path.dirname('/a/b/c'))
console.log(path.dirname('/a/b/c/')) */
复制代码
1.3 extname
获取路径的拓展名
/**
* 01 返回 path路径中相应文件的后缀名
* 02 如果 path 路径当中存在多个点,它匹配的是最后一个点,到结尾的内容
*/
/* console.log(path.extname(__filename))
console.log(path.extname('/a/b'))
console.log(path.extname('/a/b/index.html.js.css'))
console.log(path.extname('/a/b/index.html.js.')) */
复制代码
1.4 parse
解析路径
/**
* 01 接收一个路径,返回一个对象,包含不同的信息
* 02 root dir base ext name
*/
// const obj = path.parse('/a/b/c/index.html')
// const obj = path.parse('/a/b/c/')
/* const obj = path.parse('./a/b/c/')
console.log(obj.name) */
复制代码
1.5 format
序列化路径
/* const obj = path.parse('./a/b/c/')
console.log(path.format(obj)) */
复制代码
1.6 isAbsolute
判断当前的路径是否为绝对路径
/* console.log(path.isAbsolute('foo'))
console.log(path.isAbsolute('/foo'))
console.log(path.isAbsolute('///foo'))
console.log(path.isAbsolute(''))
console.log(path.isAbsolute('.'))
console.log(path.isAbsolute('../bar')) */
复制代码
1.7 join
拼接路径
/* console.log(path.join('a/b', 'c', 'index.html'))
console.log(path.join('/a/b', 'c', 'index.html'))
console.log(path.join('/a/b', 'c', '../', 'index.html'))
console.log(path.join('/a/b', 'c', './', 'index.html'))
console.log(path.join('/a/b', 'c', '', 'index.html'))
console.log(path.join('')) */
复制代码
1.8 normalize
规范化路径
/* console.log(path.normalize(''))
console.log(path.normalize('a/b/c/d'))
console.log(path.normalize('a///b/c../d'))
console.log(path.normalize('a//\\/b/c\\/d'))
console.log(path.normalize('a//\b/c\\/d')) */
复制代码
1.9 resolve
返回绝对路径
// console.log(path.resolve())
/**
* resolve([from], to)
*/
// console.log(path.resolve('/a', '../b'))
console.log(path.resolve('index.html'))
复制代码
Buffer
1.1 简介
Buffer对象用于表示固定长度的字节序列。许多 Node.js API 都支持Buffers。
Buffer最常见的用处就是作为一个缓冲区,作为文件读写的缓冲
Buffer让js可以操作二进制
1.2 Buffer是什么? 在哪? 做什么?
对文件的操作其根本就是对二进制数据的操作,对二进制数据的操作可以分为
- 流操作
- Buffer
js语言起初服务于浏览器平台,现在在Nodejs平台下js可实现io,如何实现的io就是靠着buffer和stream。
io行为操作的本质就是二进制数据
当然stream流操作并非nodejs独创
流操作配合管道实现数据分段传输,数据的端到端传输会有生产者和消费者,生产和消费的过程往往存在等待,产生等待时数据存放在哪?
Nodejs中Buffer是一片内存空间或者也可以称之为缓冲区
1.3 Buffer总结
- 无需require的一个全局变量
- 实现nodejs平台下的二进制数据操作
- 不占据v8堆内存大小的内存空间
- 内存的使用由node来控制,由v8的gc回收
- 一般配合stream流使用,充当数据缓冲区
2.1 Buffer api
2.1.1 fill
- fill 和普通js的操作一样,填充
- 三个参数,第二个参数是填充的起始位置
- 第三个参数是填充的结束位置
2.1.2 write
- 三个参数(value, start, value.length)
2.1.3 toString
- toString// 默认编码格式utf-8
- 三个参数(编码格式, start, end)
2.1.4 slice
- 两个参数(start, length) 注意编码格式不同引起的长度问题
2.1.5 copy
- source.copy(target, targetStart, sourceStart, sourceEnd)
2.1.6 ifBuffer
-
判断当前数据是否为buffer
// let buf = Buffer.alloc(6)
// fill 和普通js的操作一样,填充
// 三个参数,第二个参数是填充的起始位置
// 第三个参数是填充的结束位置
/* buf.fill(123)
console.log(buf)
console.log(buf.toString())
*/// write
// 三个参数(value, start, value.length)/* buf.write(‘123’)
console.log(buf)
console.log(buf.toString()) */// toString
// 默认编码格式utf-8
// 三个参数(编码格式, start, end)
/* buf = Buffer.from(‘拉钩教育’)
console.log(buf)
console.log(buf.toString(‘utf-8’, 3, 9)) */// slice
/* let buf = Buffer.from(‘是厕所是’)
let b1 = buf.slice()
console.log(b1.toString()) */// copy
// source.copy(target, targetStart, sourceStart, sourceEnd)
let buf = Buffer.alloc(6)
let buf2 = Buffer.from(‘拉钩’)
buf2.copy(buf, 3, 3, 6)console.log(buf.toString())
console.log(buf2.toString())
2.1.6 熟悉api,仿写split
// 原型上添加方法
ArrayBuffer.prototype.split = function (sep) {
let len = Buffer.from(sep).length
let ret = []
let start = 0
let offset = 0
while(offset = this.indexOf(sep, start) !== -1) {
ret.push(this.slice(start, offset))
start = offset + len
}
return ret
}
复制代码
fs模块
1.1 fs是NodeJs内置核心模块,提供问价系统操作的api
1.2 权限位、标识符、 文件描述符
权限位
- 用户对于文件所具备的操作权限,一般创建者对文件的权限为777
标识符
- r: 可读
- w: 可写
- s: 同步
- +: 执行相反操作
- x: 排他操作
文件描述符
- fd就是操作系统分配给被打开文件的标识
1.3 fs总结
- fs 是Nodejs中内置核心模块
- 代码层面上fs分为基本操作类和常用api
- 权限位、标识符、操作符
1.4 文件操作api
-
readFile: 从指定文件中读取数据(path, encoding,callback)
-
writeFile: 向指定文件中写入数据 (path, content, {mode: 权限位, flag: 标识符, encoding})
-
appendFile: 追加的方式向指定文件中写入数据 和writeFile一样不过是push操作
-
copyFile: 将某个文件中的数据拷贝至另一文件 (target, source, callback)
-
watchFile: 对指定文件进行监控 (path, {interval: 重复调用时间}, (curr,prev))
const fs = require(‘fs’)
const path = require(‘path’)// readFile
/* fs.readFile(path.resolve(‘data1.txt’), ‘utf-8’, (err, data) => {
console.log(err)
if (!null) {
console.log(data)
}
}) */// writeFile
/* fs.writeFile(‘data.txt’, ‘123’, {
mode: 438,
flag: ‘w+’,
encoding: ‘utf-8’
}, (err) => {
if (!err) {
fs.readFile(‘data.txt’, ‘utf-8’, (err, data) => {
console.log(data)
})
}
}) */// appendFile
/* fs.appendFile(‘data.txt’, ‘hello node.js’,{}, (err) => {
console.log(‘写入成功’)
}) */// copyFile
/* fs.copyFile(‘data.txt’, ‘test.txt’, () => {
console.log(‘拷贝成功’)
}) */// watchFile
fs.watchFile(‘data.txt’, {interval: 20}, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
console.log(‘文件被修改了’)
fs.unwatchFile(‘data.txt’)
}
})
1.5 md to html 练习
const fs = require('fs')
const path = require('path')
const marked = require('marked')
const browserSync = require('browser-sync')
/**
* 01 读取 md 和 css 内容
* 02 将上述读取出来的内容替换占位符,生成一个最终需要展的 Html 字符串
* 03 将上述的 Html 字符写入到指定的 Html 文件中
* 04 监听 md 文档内容的变经,然后更新 html 内容
* 05 使用 browser-sync 来实时显示 Html 内容
*/
let mdPath = path.join(__dirname, process.argv[2])
let cssPath = path.resolve('github.css')
let htmlPath = mdPath.replace(path.extname(mdPath), '.html')
fs.watchFile(mdPath, 0, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
fs.readFile(mdPath, 'utf-8', (err, data) => {
// 将 md--》html
let htmlStr = marked(data)
fs.readFile(cssPath, 'utf-8', (err, data) => {
let retHtml = temp.replace('{{content}}', htmlStr).replace('{{style}}', data)
// 将上述的内容写入到指定的 html 文件中,用于在浏览器里进行展示
fs.writeFile(htmlPath, retHtml, (err) => {
console.log('html 生成成功了')
})
})
})
}
})
browserSync.init({
browser: '',
server: __dirname,
watch: true,
index: path.basename(htmlPath)
})
const temp = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 1000px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 750px) {
.markdown-body {
padding: 15px;
}
}
{{style}}
</style>
</head>
<body>
<div class="markdown-body">
{{content}}
</div>
</body>
</html>
`
复制代码
1.6 文件打开和关闭
- fs.open(path, ‘r’ | ‘w’, (err, w|r fd) => {})一般分为读写了两种
- fs.close(path, callback)
- fs.read(fd, buffer, offset, length , position)
- fs.write(fd, buffer, offset, length ,position)
1.6.1 文件读写
-
所谓的读操作就是将数据从磁盘文件中写入到 buffer 中
-
fd 定位当前被打开的文件
-
buf 用于表示当前缓冲区
-
offset 表示当前从 buf 的哪个位置开始执行写入
-
length 表示当前次写入的长度
-
position 表示当前从文件的哪个位置开始读取
const fs = require(‘fs’)
// read : 所谓的读操作就是将数据从磁盘文件中写入到 buffer 中
let buf = Buffer.alloc(10)/**
- fd 定位当前被打开的文件
- buf 用于表示当前缓冲区
- offset 表示当前从 buf 的哪个位置开始执行写入
- length 表示当前次写入的长度
- position 表示当前从文件的哪个位置开始读取
/
/ fs.open(‘data.txt’, ‘r’, (err, rfd) => {
console.log(rfd)
fs.read(rfd, buf, 1, 4, 3, (err, readBytes, data) => {
console.log(readBytes)
console.log(data)
console.log(data.toString())
})
}) */// write 将缓冲区里的内容写入到磁盘文件中
buf = Buffer.from(‘1234567890’)
fs.open(‘b.txt’, ‘w’, (err, wfd) => {
fs.write(wfd, buf, 2, 4, 0, (err, written, buffer) => {
console.log(written, ‘—-‘)
fs.close(wfd)
})
})
1.6.2 大文件的写入测试
const fs = require('fs')
/**
* 01 打开 a 文件,利用 read 将数据保存到 buffer 暂存起来
* 02 打开 b 文件,利用 write 将 buffer 中数据写入到 b 文件中
*/
let buf = Buffer.alloc(10)
// 01 打开指定的文件
/* fs.open('a.txt', 'r', (err, rfd) => {
// 03 打开 b 文件,用于执行数据写入操作
fs.open('b.txt', 'w', (err, wfd) => {
// 02 从打开的文件中读取数据
fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
// 04 将 buffer 中的数据写入到 b.txt 当中
fs.write(wfd, buf, 0, 10, 0, (err, written) => {
console.log('写入成功')
})
})
})
}) */
// 02 数据的完全拷贝
/* fs.open('a.txt', 'r', (err, rfd) => {
fs.open('b.txt', 'a+', (err, wfd) => {
fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
fs.write(wfd, buf, 0, 10, 0, (err, written) => {
fs.read(rfd, buf, 0, 5, 10, (err, readBytes) => {
fs.write(wfd, buf, 0, 5, 10, (err, written) => {
console.log('写入成功')
})
})
})
})
})
}) */
const BUFFER_SIZE = buf.length
let readOffset = 0
fs.open('a.txt', 'r', (err, rfd) => {
fs.open('b.txt', 'w', (err, wfd) => {
function next () {
fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => {
if (!readBytes) {
// 如果条件成立,说明内容已经读取完毕
fs.close(rfd, ()=> {})
fs.close(wfd, ()=> {})
console.log('拷贝完成')
return
}
readOffset += readBytes
fs.write(wfd, buf, 0, readBytes, (err, written) => {
next()
})
})
}
next()
})
})
复制代码
1.7 目录操作api
-
access: 判断文件或目录是否具有操作权限(path, (err))
-
stat: 获取目录及文件信息(path, (err, statObj))
-
mkdir: 创建目录(path, {recursive: 递归创建})
-
rmdir: 删除目录(path, {recursive: 递归删除})
-
readdir: 读取文件中的内容(path, (err, file: path下的子集目录))
-
unlink: 删除指定文件(path, (err))
const fs = require(‘fs’)
// 一、access
/* fs.access(‘a.txt’, (err) => {
if (err) {
console.log(err)
} else {
console.log(‘有操作权限’)
}
}) */// 二、stat
/* fs.stat(‘a.txt’, (err, statObj) => {
console.log(statObj.size)
console.log(statObj.isFile())
console.log(statObj.isDirectory())
}) */// 三、mkdir
/* fs.mkdir(‘a/b/c’, {recursive: true}, (err) => {
if (!err) {
console.log(‘创建成功’)
}else{
console.log(err)
}
}) */// 四、rmdir
fs.rmdir(‘a’, {recursive: true}, (err) => {
if (!err) {
console.log(‘删除成功’)
} else {
console.log(err)
}
})// 五、readdir
/* fs.readdir(‘a/b’, (err, files) => {
console.log(files)
}) */// 六、unlink
/* fs.unlink(‘a/a.txt’, (err) => {
if (!err) {
console.log(‘删除成功’)
}
}) */
1.7.1 创建目录的同步实现
const fs = require('fs')
// 一、access
/* fs.access('a.txt', (err) => {
if (err) {
console.log(err)
} else {
console.log('有操作权限')
}
}) */
// 二、stat
/* fs.stat('a.txt', (err, statObj) => {
console.log(statObj.size)
console.log(statObj.isFile())
console.log(statObj.isDirectory())
}) */
// 三、mkdir
/* fs.mkdir('a/b/c', {recursive: true}, (err) => {
if (!err) {
console.log('创建成功')
}else{
console.log(err)
}
}) */
// 四、rmdir
fs.rmdir('a', {recursive: true}, (err) => {
if (!err) {
console.log('删除成功')
} else {
console.log(err)
}
})
// 五、readdir
/* fs.readdir('a/b', (err, files) => {
console.log(files)
}) */
// 六、unlink
/* fs.unlink('a/a.txt', (err) => {
if (!err) {
console.log('删除成功')
}
}) */
复制代码