Node.Js
Node.js
一. 简介
Node.js 是一个开源与跨平台的 JavaScript 运行时环境。
Node.js 在浏览器外运行 V8 JavaScript 引擎(Chrome 的内核)
二、命令行运行
node app.js
三、模块
Node.js提供了一个简单的模块系统让Node.js的文件可以相互调用。模块加载采用的是同步加载的commonjs规范
commonjs:
- 每个文件都是封闭的一个模块,模块里定义的变量、函数、类都是私有的
- module代表当前模块,module是封闭的,但它的exports属性向外提供调用接口
- require加载模块,读取并执行一个js文件,然后返回该模块的exports对象
- commonjs是同步加载的,因此模块加载的顺序严格按照代码书写的顺序执行
- 模块可以多次加载,但在第一次加载之后模块会被编译执行,放入缓存,后续的require直接从缓存里取值,模块代码不再编译执行
require内部处理流程
- 检查Module._cache是否缓存了指定模块
- 如果缓存没有的话,就创建一个新的module实例将它保存到缓存
- module.load()加载指定模块
- 在解析的过程中如果发生异常,就从缓存中删除该模块
- 返回该模块的module.exports
模块化导出方式
- global.address = 'beijing';//导出全局变量,只要导入相应js文件即可调用
- module.exports = "str";//可以
- module.exports.msg = 'str'//可以
- exports.msg = 'str'//可以
- 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~~Tina')
})
server.listen(port, () => {
console.log(`服务器运行了`)
})
1.http.server
通过http.createServer
或者new http.Server()
可以创建一个http.server实例
- close 停止服务
- **listen **启动服务器监听
2.response响应体
-
write
向前端返回数据,该方法可调用多次,返回的数据会被拼接到一起
-
end
结束请求,同时
response.end
方法也可以用来向前端返回数据 -
setHeader
设置响应头,如果该头信息已存在,则覆盖
-
statusCode
设置响应状态
-
statusMessage
设置响应状态信息
2.请求方式
-
request
-
get
两种请求都接受以下参数:
url
请求地址options
请求配置callback
请求成功回调
其中options
请求配置包括:
host: 表示请求网站的域名或IP地址(请求的地址)。 默认为'localhost'。
hostname: 服务器名称,主机名是首选的值。
port: 请求网站的端口,默认为 80。
method: HTTP请求方法,默认是 ‘GET'。(http.get时值只能为"GET")
path: 请求的相对于根的路径,默认是'/'。QueryString应该包含在其中。例如:/index.html?page=12
headers: 请求头对象
五、fs
在 NodeJS 中,所有与文件操作都是通过 fs 核心模块来实现的,包括文件目录的创建、删除、查询以及文件的读取和写入,在 fs 模块中,所有的方法都分为同步和异步两种实现,具有 sync 后缀的方法为同步方法,不具有 sync 后缀的方法为异步方法
1.文件读取
const fs = require("fs");
// 同步读取 readFileSync
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
// 异步读取 readFile
fs.readFile("demo.txt", "utf8", (err, data) => {
console.log(err); // null
console.log(data); // Hello
});
2.文件写入
const fs = require("fs");
// 同步写入 writeFileSync
fs.writeFileSync("demo.txt", "Hello world");
let data = fs.readFileSync("demo.txt", "utf8");
console.log(data); // Hello world
// 异步写入 writeFile
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");
// 同步追加 appendFileSync
fs.appendFileSync("demo.txt", " world");
let data = fs.readFileSync("demo.txt", "utf8");
console.log(data); // Hello world
// 异步追加 appendFile
fs.appendFile("3.txt", " world", err => {
if (!err) {
fs.readFile("demo.txt", "utf8", (err, data) => {
console.log(data); // Hello world
});
}
});
4.文件拷贝写入
const fs = require("fs");
// 同步拷贝 copyFileSync
fs.copyFileSync("demo.txt", "newdemo.txt");
let data = fs.readFileSync("newdemo.txt", "utf8");
console.log(data); // Hello world
// 异步拷贝 copyFile
fs.copyFile("demo.txt", "newdemo.txt", () => {
fs.readFile("newdemo.txt", "utf8", (err, data) => {
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");
// 同步读取目录
let data = fs.readdirSync("a/b");
console.log(data); // [ 'c', 'index.js' ]
// 异步读取目录
fs.readdir("a/b", (err, data) => {
if (!err){
console.log(data);
}
});
7.删除文件目录
删除的目录需要是空文件夹
const fs = require("fs");
// 同步删除目录
fs.rmdirSync("a/b");
// 同步删除文件
fs.rmdir("a/b", err => {
if (!err) console.log("删除成功");
});
8.删除文件操作
const fs = require("fs");
// 同步删除文件
fs.unlinkSync("a/index.js");
// 异步删除文件
fs.unlink("a/index.js", err => {
if (!err) console.log("删除成功");
});
fs常用方法:
fs.access()
: 检查文件是否存在,以及 Node.js 是否有权限访问。fs.appendFile()
: 追加数据到文件。如果文件不存在,则创建文件。fs.chmod()
: 更改文件(通过传入的文件名指定)的权限。相关方法:fs.lchmod()
、fs.fchmod()
。fs.chown()
: 更改文件(通过传入的文件名指定)的所有者和群组。相关方法:fs.fchown()
、fs.lchown()
。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.fstat()
、fs.lstat()
。fs.symlink()
: 新建文件的符号链接。fs.truncate()
: 将传递的文件名标识的文件截断为指定的长度。相关方法:fs.ftruncate()
。fs.unlink()
: 删除文件或符号链接。fs.unwatchFile()
: 停止监视文件上的更改。fs.utimes()
: 更改文件(通过传入的文件名指定)的时间戳。相关方法:fs.futimes()
。fs.watchFile()
: 开始监视文件上的更改。相关方法:fs.watch()
。fs.writeFile()
: 将数据写入文件。相关方法:fs.write()
。
六、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', '.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模块只提供了一个对象events.EventEmitter,EventEmitter 的核心是事件发射与事件监听器。
Node.js中大部分的模块,都继承自Event模块。
EventEmitter 支持若干个事件监听器。当事件发射时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。
1.addListener() / on()
为事件注册一个监听
-
参数1:事件名
-
参数2:回调函数
const EventEmitter = require('events')
const door = new EventEmitter()
//注册一个叫connection的事件,事件执行函数是后面的后调函数
door.on('connection', (stream) => {
console.log('someone connected!');
});
door.emit("connection")
2.emit()
触发事件。 按照事件被注册的顺序同步地调用每个事件监听器。
- 参数1:事件名
- 参数2:事件回调函数需要的参数
- 。。。
door.emit("connection")
3.once()
添加当事件在注册之后首次被触发时调用的回调函数。 该回调只会被调用一次,不会再被调用。
-
参数1:事件名
-
参数2:回调函数
door.once('my-event', () => {
//只调用一次回调函数。
console.log('my-event被调用')
})
door.emit("my-event")
door.emit("my-event")
4.eventNames()
返回包含当前EventEmitter
对象上注册的事件的数组:
door.eventNames()
5.removeListener()
移除特定的监听器。
- 参数1:要移除的事件名
- 参数2:事件名对应的事件回调(需要是注册时的那个事件)
const doSomething = () => {}
door.on('open', doSomething)
door.removeListener('open', doSomething)
6.removeAllListeners()
移除 EventEmitter
对象的所有监听特定事件的监听器
-
参数1:事件名
-
如果不传时间名,则移除所有事件
door.removeAllListeners('open')
7.listeners
获取作为参数传入的事件监听器的数组:
- 参数1:需要获取的事件名
door.listeners('open')
8.setMaxListeners()
置可以添加到 EventEmitter
对象的监听器的最大数量(默认为 10,但可以增加或减少)
door.setMaxListeners(10)
八、buffer(缓冲区)
Buffer 是内存区域。它表示在 V8 JavaScript 引擎外部分配的固定大小的内存块(无法调整大小)。
可以将 buffer 视为整数数组,每个整数代表一个数据字节。
Buffer 被引入用以帮助开发者处理二进制数据,在此生态系统中传统上只处理字符串而不是二进制数据。
Buffer 与流紧密相连。 当流处理器接收数据的速度快于其消化的速度时,则会将数据放入 buffer 中。
一个简单的场景是:当观看 YouTube 视频时,红线超过了观看点:即下载数据的速度比查看数据的速度快,且浏览器会对数据进行缓冲。
1.创建buffer
-
Buffer.from() 返回新的
Buffer
const Buffer = require('buffer').Buffer; // 创建包含字符串 'buffer' 的 UTF-8 字节的新缓冲区。 const buf = Buffer.from([ 0x68, 0x65, 0x6c, 0x6c, 0x6f]); var str = buf.toString("utf-8"); // hello const buf = Buffer.from('Hey!') //<Buffer 48 65 79 21>
-
Buffer.alloc 返回指定大小的新初始化
Buffer
。 此方法比 Buffer.allocUnsafe(size) 慢,但保证新创建的Buffer
实例永远不会包含可能敏感的旧数据。 如果size
不是数值,则会抛出TypeError
。const buf = Buffer.alloc(5); console.log(buf); // 打印: <Buffer 00 00 00 00 00>
-
Buffer.allocUnsafe(size) 返回指定
size
的新的未初始化的Buffer
。const buf = Buffer.allocUnsafe(10); console.log(buf); // <Buffer a0 8b 28 3f 01 00 00 00 50 32>
2.使用 buffer
Buffer(字节数组)可以像数组一样被访问:
const buf = Buffer.from('Hey!')
console.log(buf[0]) //72
console.log(buf[1]) //101
console.log(buf[2]) //121
这些数字是 Unicode 码,用于标识 buffer 位置中的字符(H => 72、e => 101、y => 121)。
可以使用 toString()
方法打印 buffer 的全部内容:
console.log(buf.toString())
3.获取 buffer 的长度
const buf = Buffer.from('Hey!')
console.log(buf.length)
迭代 buffer 的内容
const buf = Buffer.from('Hey!')
for (const item of buf) {
console.log(item) //72 101 121 33
}
4.更改 buffer 的内容
可以使用 write()
方法将整个数据字符串写入 buffer:
const buf = Buffer.alloc(4)
buf.write('Hey!')
就像可以使用数组语法访问 buffer 一样,你也可以使用相同的方式设置 buffer 的内容:
const buf = Buffer.from('Hey!')
buf[1] = 111 //o
console.log(buf.toString()) //Hoy!
5.复制 buffer
使用 copy()
方法可以复制 buffer:
const buf = Buffer.from('Hey!')
let bufcopy = Buffer.alloc(4) //分配 4 个字节。
buf.copy(bufcopy)
默认情况下,会复制整个 buffer。 另外的 3 个参数可以定义在目标对象开始填充位置、复制对象的开始位置、复制对象的结束位置:
const buf = Buffer.from('Hey!')
let bufcopy = Buffer.alloc(2) //分配 2 个字节。
buf.copy(bufcopy, 0, 0, 2)
bufcopy.toString() //'He'
6.切片 buffer
如果要创建 buffer 的局部视图,则可以创建切片。 切片不是副本:原始 buffer 仍然是真正的来源。 如果那改变了,则切片也会改变。
使用 slice()
方法创建它。 第一个参数是起始位置,可以指定第二个参数作为结束位置:
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
九、流
流是数据的集合 - 就像数组或者字符串。他们之间的区别是流可能不是一次性获取到的,它们不需要匹配内存。这让流在处理大容量数据,或者从一个额外的源每次获取一块数据的时候变得非常强大。
流基本上提供了两个主要优点:
- 内存效率: 无需加载大量的数据到内存中即可进行处理。
- 时间效率: 当获得数据之后即可立即开始处理数据,这样所需的时间更少,而不必等到整个数据有效负载可用才开始。
几乎所有的 Node.js 应用程序,无论多么简单,都以某种方式使用流
Node.js,Stream 有四种流类型:
- Readable - 可读操作。
- Writable - 可写操作。
- Duplex - 可读可写操作.
- Transform - 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
-
data - 当有数据可读时触发。
-
end - 没有更多的数据可读时触发。
-
error - 在接收和写入过程中发生错误时触发。
-
finish - 所有数据已被写入到底层系统时触发。
1.读取流
读取流包括:
创建流对象:
const Stream= require('stream');
//创建读取流对象
const readableStream = new Stream.Readable();
//实现_read方法 所有 Readable 流实现都必须提供 readable._read() 方法的实现
readableStream._read = ()=>{}
//推入读取队列的数据块
readableStream.push('tina')
readableStream.push('你好')
readableStream.on('data', function(data) {
console.log("写入完成。",data.toString());
});
var fs = require("fs");
var data = '';
// 创建可读流
var readStream = fs.createReadStream('demo.txt');
// 设置编码为 utf8。
readStream.setEncoding('UTF8');
readStream.on("open",(data)=>{
console.log('打开了',data)
})
readStream.on("data",(data)=>{
console.log("数据来了!");
console.log("已经读取的字节数",readStream.bytesRead);
})
readStream.on("close",(data)=>{
console.log('close')
})
console.log("程序执行完毕");
2.写入流
写入流的包括:
创建写入流:
若要创建可写流,需要继承基本的 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 fs = require("fs");
var data = '我是要写入的内容';
// 创建一个可以写入的流,写入到文件 demo2.txt 中
var writerStream = fs.createWriteStream('demo2.txt');
// 使用 utf8 编码写入数据
writerStream.write(data,'UTF8');
// 标记文件末尾
writerStream.end();
// 处理流事件 --> finish、error
writerStream.on('finish', function() {
console.log("写入完成。");
});
writerStream.on('error', function(err){
console.log(err.stack);
});
console.log("程序执行完毕");
3.可读写流
const Stream = require('stream')
//创建可读流
const readableStream = new Stream.Readable({
read() {}
})
//创建写入流
const writableStream = new Stream.Writable()
//实现_write方法
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
//讲可读流里的数据流入可写流
readableStream.pipe(writableStream)
//可读流push数据
readableStream.push('hi!')
readableStream.push('ho!')
//可读流内可读的数据输出
readableStream.on('readable', () => {
console.log(readableStream.read().toString())
})
//可写流写入数据
writableStream.write('hey!\n')
Node特点
1.异步非阻塞I/O
在Node中,绝大多数的操作都以异步的方式进行调用,从文件读取到网络请求,均是如此,异步I/O意味着每个调用之间无须等待之前的I/O调用结束。
2.事件回调
在Node中回调也是无处不在的,事件的处理基本都是依赖回调来实现的,在JavaScript中,可以将函数作为对象传递给方法作为实参进行调用。
3.单线程
单线程的运行会导致无法利用多核CPU,一旦程序发生错误就会引起整个程序退出,并且大量的计算会占用CPU从而阻塞后续的程序运行
Node实质上是给javaScript提供了一个处web以外的运行环境,所以我们其实也是在Node内编写javaScript代码。
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,组织数据进行返回即可。它本质上只是从某个数据库中查找一些值并将它们组成一个响应。由于响应是少量文本,入站请求也是少量的文本,因此流量不高,可以轻松应对。