一、简介
Node.js 是一个开源和跨平台的 JavaScript 运行时环境。
Node.js 在浏览器之外运行 V8 JavaScript 引擎(Google Chrome 的内核)。 这使得 Node.js 的性能非常好。
二、命令行运行
node app.js
三、模块
Node提供了一个简单的模块系统让Nodejs的文件可以相互调用。模块加载采用的是同步加载的commonjs规范
commonjs:
- 每个文件都是封装的一个模块,模块里定义的变量、函数、类都是私有的
- module代表当前模块、默读了是封闭,但它的exports属性向外提供调用接口
- require 加载模块,读取并执行一个js文件,然后返回该模块的exports对象
- commonjs是同步加载的,因此模块加载的顺序严格按照代码执行顺序
- 模块可以多次加载,但是第一次加载之后模块会被编译执行,放入缓存,后续的require直接从缓存里取值,模块代码不在编译执行
require内部处理流程
- 检查Module._cache 是否缓存了置顶模块
- 如果缓存没有的话,就创建一个新的默读了实例将它保存到缓存
- module.load()加载指定模块
- 在解析的过程中如果发生异常,九从缓存中删除该模块
- 返回该模块的module.exports
模块化导入方式
- global.address = ‘zhengzhou’ // 全局变量
- module.exports = ‘str’
- module.exports.msg = ‘str’
- exports.msg = ‘str’ // module.exports.msg 简写
exports = ‘str’// 错误
四、HTTP
Node内置模块HTTP可以用来创建HTTP服务器
要使用HTTP服务器和客户端,则必须require(‘http’)
const http = require('http')
const port = 3000
const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-type', 'text/plain; charset=utf8')
res.end('hi--alhuo')
})
server.listen(port, () => {
console.log('服务器启动了')
})
1. http.server
通过http.createServer或者new http.Server() 可以创建一个http.server实例
- close() 暂停服务
- listen 启动服务器监听
2.request响应体
- write
向前端返回数据,该方法可调用多次,返回的数据会被凭借到一起 - end
接收请求,同时response.end 方法也可以用来向前端返回数据 - setHeader
设置响应头,如果该头信息已存在,则覆盖 - statusCode
设置响应状态 - statusMessage
设置响应状态信息
3.请求方式
- request
- get
两种请求都接受以下参数- url 请求地址
- options请求配置
- callback 请求成功回调
options请求配置包括
- host:表示请求网站的域名或者IP地址,默认为localhost
- hostname:服务器域名,主机名是首选的值
- port:请求网站的端口,默认为80
- method:HTTP请求方法,默认是GET(http.get时值只有GET)
- path:请求的相对根的路径,默认是/ QueryString应该包含在其中,例如:/index.html?page=1
- headers:请求头对象
五、fs
在Nodejs中,所有与文件操作都是通过fs核心模块来实现的,包括文件目录的创建,删除,查询以及文件的读取和写入,在fs模块中,所有的方法都分为同步和异步两种实现。带sync的为同步。
1.文件读取
const fs = require('fs')
// 同步读取
let buf = fs.readFileSync('demo.txt')
let data = fs.readFileSync('demo.txt', 'utf8')
console.log(buf) // <Buffer 48 65 6c 6c 6f>
console.log(data) // Hello
// 异步读取
fs.readFile('demo.txt', 'utf8', (err, data) => {
console.log(err) // null
console.log(data) // Hello
})
2.文件写入
const fs = require('fs')
// 同步写入
fs.writeFileSync('demo.txt', "hello world")
let data = fs.readFileSync('demo.txt', 'utf8')
console.log(data) // hello world
// 异步写入
fs.writeFile('demo.txt', 'hello world', err => {
if (!err) {
fs.readFile('demo.txt', 'utf8', (err, data) => {
console.log(data) // hello world
})
}
})
3.追加写入
const fs = require('fs')
// 同步追加
fs.appendFileSync('demo.txt', "world")
let data = fs.readFileSync('demo.txt', 'utf8')
console.log(data) // hello world
// 异步追加
fs.appendFile('demo.txt', 'world', err => {
if (!err) {
fs.readFile('demo.txt', 'utf8', (err, data) => {
console.log(data) // hello world
})
}
})
4.文件拷贝写入
const fs = require('fs')
// 同步拷贝
fs.copyFileSync('demo.txt', "newdemo.txt")
let data = fs.readFileSync('newdemo.txt', 'utf8')
console.log(data) // hello world
// 异步拷贝
fs.copyFile('demo.txt', 'newdemo.txt', err => {
if (!err) {
fs.readFile('newdemo.txt', 'utf8', () => {
console.log(data) // hello world
})
}
})
5.创建目录
const fs = require('fs')
// 同步创建文件目录a/b
fs.mkdirSync("a/b")
// 在b目录下创建c.text,其实创建的还是一个目录
fs.mkdirSync("a/b/c.text")
// 异步创建目录
fs.mkdir('a/b/c', err => {
if (!err) {
console.log('创建成功')
}
})
6.创建文件
const fs = require('fs')
// 同步 a目录下创建demo.txt
fs.writeFileSync('a/demo.txt', "hello world")
// 异步同理
...
7.读取文件目录
const fs = require('fs')
// 同步读取目录
let data = fs.readdirSync('a/b')
console.log(data) // ['c', 'index.js']
// 异步读取目录
fs.readdir('a/b', , (err,data) => {
if (!err) {
console.log(data) // ['c', 'index.js']
}
})
8.删除文件目录
const fs = require('fs')
// 同步删除
let data = fs.rmdirSync('a/b')
// 异步删除
fs.readdir('a/b', , (err,data) => {
if (!err) {
}
})
9.删除文件
const fs = require('fs')
// 同步删除
let data = fs.unlinkSync('a/index.js')
// 异步删除
fs.unlink('a/index.js', , (err,data) => {
if (!err) {
}
})
fs常用方法
- fs.access() 检查文件是否存在,以及nodejs是否有权限访问
- fs.appendFile() 最近数据到文件,如果文件不存在,则创建文件
- fs.chmod() 更改文件的权限,相关方法(fs.lchmod, fs.fchmod)
- fs.chown() 更改文件的所有者和群组,相关方法(fs.lchown, fs.fchown)
- fs.close 关闭文件描述符
- fs.copyFile() 拷贝文件
- fs.createReadStream() 创建可读的文件流
- fs.createWriteStream() 创建可写的文件流
- fs.link() 新增指向文件的硬链接
- fs.mkdir 新建文件夹
- fs.mkdtemp 创建临时目录
- fs.open() 设置文件模式
- fs.readdir() 读取目录内容
- fs.readFile() 读取文件内容,相关方法:fs.read()
- fs.readlink() 读取符号链接的值
- fs.realpath() 将相对的文件路径指针解析为完整的路径
- fs.rename() 重命名文件或文件夹
- fs.rmdir() 删除文件夹
- fs.stat() 返回文件的状态,相关方法(fs.lstat, fs.fstat)
- fs.symlink() 新建文件的符号链接
- fs.truncate() 将传递的文件名标识的文件截断为指定的长度,相关方法fs.ftruncate()
- fs.unlink() 删除文件或符号链接
- fs.unwatchFile() 停止监听文件上的更改
- fs.utime() 改改文件的时间戳
- fs.watchFile() 监听文件更改
- fs.writeFile() 写入文件
六、path
path模块提供了许多函数来访问文件系统并与文件系统进行交互
1. path.basename()
返回路径的最后一部分,第二个参数可以过滤掉文件的扩展名
require('path').basename('/test/something') // something
require('path').basename('/test/something.txt') // something.txt
require('path').basename('/test/something', '.txt') // something
2. path.dirname()
返回路径的目录部分
require('path').dirname('/test/something') // test
require('path').dirname('/test/something/file.txt') // /test/something
3. path.extname()
返回路径的扩展名部分
require('path').extname('/test/something') // ''
require('path').extname('/test/something/file.txt') // '.txt'
4. path.isAbsolute()
如果是绝对路径,则返回 true
require('path').isAbsolute('/test/something') // true
require('path').isAbsolute('./test/something') // false
5. path.join()
连接路径的两个或多个部分
const name = 'joe'
require('path').join('/', 'users', name, 'notes.txt') //'/users/joe/notes.txt'
6. path.normalize()
当包含类似 .、… 或双斜杠等相对的说明符时,则尝试计算实际的路径
require('path').normalize('/users/joe/..//test.txt') //'/users/test.txt'
7. path.parse()
解析对象的路径为组成其的片段:
- root: 根路径。
- dir: 从根路径开始的文件夹路径。
- base: 文件名 + 扩展名
- name: 文件名
- ext: 文件扩展名
require('path').parse('/users/test.txt')
/*
{
root: '/',
dir: '/users',
base: 'test.txt',
ext: '.txt',
name: 'test'
}
*/
8. path.relative()
接受 2 个路径作为参数。 基于当前工作目录,返回从第一个路径到第二个路径的相对路径
require('path').relative('/Users/joe', '/Users/joe/test.txt') //'test.txt'
require('path').relative('/Users/joe', '/Users/joe/something/test.txt') //'something/test.txt'
9. path.resolve()
可以使用 path.resolve() 获得相对路径的绝对路径计算
path.resolve('joe.txt') //'/Users/joe/joe.txt' 如果从主文件夹运行
通过指定第二个参数,resolve 会使用第一个参数作为第二个参数的基准:
path.resolve('tmp', 'joe.txt') //'/Users/joe/tmp/joe.txt' 如果从主文件夹运行
如果第一个参数以斜杠开头,则表示它是绝对路径:
path.resolve('/etc', 'joe.txt') //'/etc/joe.txt'
七、事件模块
events 模块为提供了 EventEmitter 类,这是在 Node.js 中处理事件的关键
const EventEmitter = require('events')
const door = new EventEmitter()
事件监听器返回及使用以下事件
- 当监听器被添加时返回 newListener
- 当监听器被移除时返回 removeListener
emitter.addListener()
emitter.on() 的别名
emitter.emit()
触发事件。 按照事件被注册的顺序同步地调用每个事件监听器
door.emit("slam") // 触发 "slam" 事件。
emitter.eventNames()
返回字符串(表示在当前 EventEmitter 对象上注册的事件)数组
door.eventNames()
emitter.getMaxListeners()
获取可以添加到 EventEmitter 对象的监听器的最大数量(默认为 10,但可以使用 setMaxListeners() 进行增加或减少)
door.getMaxListeners()
emitter.listenerCount()
获取作为参数传入的事件监听器的计数
door.listenerCount('open')
emitter.listeners()
获取作为参数传入的事件监听器的数组
door.listeners('open')
emitter.off()
emitter.removeListener() 的别名,新增于 Node.js 10
emitter.on()
添加当事件被触发时调用的回调函数
door.on('open', () => {
console.log('打开')
})
emitter.once()
添加当事件在注册之后首次被触发时调用的回调函数。 该回调只会被调用一次,不会再被调用
const EventEmitter = require('events')
const ee = new EventEmitter()
ee.once('my-event', () => {
//只调用一次回调函数。
})
emitter.prependListener()
当使用 on 或 addListener 添加监听器时,监听器会被添加到监听器队列中的最后一个,并且最后一个被调用。 使用 prependListener 则可以在其他监听器之前添加并调用。
emitter.prependOnceListener()
当使用 once 添加监听器时,监听器会被添加到监听器队列中的最后一个,并且最后一个被调用。 使用 prependOnceListener 则可以在其他监听器之前添加并调用
emitter.removeAllListeners()
移除 EventEmitter 对象的所有监听特定事件的监听器
door.removeAllListeners('open')
emitter.removeListener()
移除特定的监听器。 可以通过将回调函数保存到变量中(当添加时),以便以后可以引用它
const doSomething = () => {}
door.on('open', doSomething)
door.removeListener('open', doSomething)
emitter.setMaxListeners()
设置可以添加到 EventEmitter 对象的监听器的最大数量(默认为 10,但可以增加或减少)
door.setMaxListeners(50)
八、buffer
Buffer是内存区域。它表示在V8 JavaScript 引擎外部分配的固定大小的内存(无法调整大小)
可以将Buffer视为整数数组,每个整数代表一个数据字节
Buffer被引入用以帮助开发者处理二进制数据,在此生态系统中传统上只处理字符串而不是二进制数据。
Buffer与流紧密相连,当流处理器接受数据的速度快于其消化的速度,则会把数据放入buffer中。
一个简单的场景:当观看YouTube视频时,红线超过了观看点:即下砸数据的速度比查看数据的速度快时,切浏览器会对数据进行缓冲。
1. 创建buffer
Buffer.from() 返回新的Buffer
const Buffer = require('buffer').Buffer
// 创建包含字符串‘hello’ 的 UTF-8字节的新缓冲区
const buf = Buffer.from([0x68, 0x65, 0x6c, 0x6c 0x6f])
var str = buf.toString('utf-8') // hello,默认是utf-8
// 创建初始化 buffer(传入大小)字节
const buf = Buffer.alloc(1024, '嗨喽', 'utf-8')
// 打印出来乱码,并且不能设置编码,只有一个参数,底层不会被初始化,有旧数据不安全
const buf = Buffer.allocUnsafe(1024)
// 获取长度
console.log(buf.length)
// 遍历缓冲区
for (const i of buf) {
console.log(i)
编码遍历之后转字符串
Buffer.from([i].toString()
}
// 替换buf中的内容 write,改写时buffer内存不足时,**只能填进去一部分**
cont bufstr = Buffer.from('Hey!')
bufstr.write('帅哥')
// 复制一个buffer, 必须先创建再复制
const bufstr2 = Buffer.alloc(4)
bufstr2.copy(bufstr)
// 切片 buffer
// 如果要创建 buffer 的局部视图,则可以创建切片。 切片不是副本:原始 buffer 仍然是真正的来源。 如果那改变了,则切片也会改变
const buf = Buffer.from('Hey!')
buf.slice(0).toString() //Hey!
const slice = buf.slice(0, 2)
console.log(slice.toString()) //He
buf[1] = 111 //o
console.log(slice.toString()) //Ho
九、流
流是数据的集合,就像数组或者字符串,他们之间的区别就是流可能不是一次性获取到的,他们不需要匹配内存,这让流在处理大容量数据,或者从一个额外的源每次获取一块数据的时候变大非常强大。
流的优点:
- 内存效率:无需加载大量的数据到内存中即可进行处理
- 时间效率:当获得数据之后即可立即开始处理数据,这样所需的时间更少,而不必等到整个数据有效负载可用才开始
NodeJs,Stream 有四种流类型: - Readable 可读操作
- Writeable 可写操作
- Duplex 可读写操作
- Transform 操作被写入数据,然后读出结果
所有的Stream对象都是EventEmitter的实例。常用的事件有:
- data 当有数据可读时触发
- end 没有更多数据可读时触发
- error 在接收和写入过程中发生错误触发
- finish 所有数据已被写入到底层系统时触发
1.读取流
读取流包括
- 客户端上的HTTP响应
- 服务器上的HTTP请求
- 文件系统读取流
- 压缩流
- 加密流
- …
创建流对象
const Stream = require('stream')
// 创建读取流对象
const readableStream = new Stream.Readable()
// 实现_read方法,所有Readable实现都必须提供readable._read() 方法的实现
readableStream._read = () => {}
readableStream.push('放入0')
readableStream.on('data', (str) => {
console.log(str.toString()) // 放入0 放入1
})
readableStream.push('放入1')
以最高效率获取文件内容
let infoStr = ''
var infoTextStream = fs.createReadStream('info.txt')
infoTextStream.setEncoding('utf8')
infoTextStream.on('open', () => {
console.log('文件打开了')
})
infoTextStream.on('data', (str) => {
console.log(str)
})
infoTextStream.on('error', (err) => {
console.log(err)
})
// close 或者 end 一样
infoTextStream.on('close', () => {
console.log('close')
})
2.写入流
写入流包括
- 客户端上的HTTP请求
- 服务器上的HTTP响应
- 文件系统写入流
- 压缩流
- 加密流
- …
创建写入流
若要创建可写流,需要继承基本的writable对象,并实现其_write() 方法
const Stream = require('stream')
// 创建写入流对象
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
writableStream.write('hello', () => {
console.log('写入了hello')
})
写入文档流
var WriteStream = fs.createWriteStream('info.txt')
WriteStream.write('我是写入的流', 'utf8')
WriteStream.write('我是写入的流1', 'utf8')
WriteStream.end() // 写入完成
WriteStream.on('finish', () => {
})
WriteStream.on('error', (err) => {
})
3.读写流
const readableStream = new Stream.Readable({
read () {}
})
const writableStream = new Stream.Writable({
write(chunk, encoding, next) {
console.log(chunk.toString())
next()
}
})
// 读取流通过管道流入写入流
readableStream.pipe(writableStream)
// 读取流中推入数据
readableStream.push('hey')
readableStream.push('hey1')
// 也可以使用 readable 事件直接地消费可读流
readableStream.on('readable', () => {
console.log(readableStream.read())
})
node 特点
1.异步非阻塞I/O
在Node中,绝大多数的操作都以一步的方式进行调用,从文件读取到网络请求,,异步I/O意味着每个调用之间无需等待之前的I/O调用结束
2.事件回调
在Node中回调也是无处不在,事件的处理基本都是依赖回调来实现的,在JS中,可以将函数作为对象传递给方法作为实参进行调用
3.单线程
单线程的运行会导致无法利用多核CPU,一旦程序发生错误就会引起整个程序退出,并且大量的计算会占用CPU从而阻塞后续的程序运行
Node是指上就是给JS提供一个web以外的运行环境,所以我们其实也是在Node内编写JS
Node内的I/O事件是异步的,整个事件驱动过程中不会阻塞新的任务发起,理论上NodeJs能支持比Java、PHP程序更高的并发量,虽然维护事件队列也需要成本,再由于NodeJs是单线程,事件队列越长,得到的响应的时间就越长。
node 使用场景
1. I/O密集
Node异步I/O的特点可以轻松面对I/O密集型的业务场景,处理效率将比同步I/O高,虽然同步I/O可以采用多线程或者多进程的方式进行,但是相比Node自身异步I/O的特性来说,静增加对内存和CPU的开销,但是由于Node是单线程,所有如果有长时间运行的计算(比如大循环),将会导入CPU时间片不能释放,使得后面I/O无法发起
解决方案:分解大型运算任务为多个小任务,使得运算能够适当释放,不阻塞I/O调用的发起
2. 高并发
Node可以处理万条连接,本身没有太多的逻辑,只需要请求API,组织数据进行返回即可,它本质上只是从某个数据库中查找一些值并将它们组成一个响应,由于响应市少量文本,入站请求也是少量文本,因此流量不高,可轻松应对。
参考网站:http://nodejs.cn/learn