文件操作open
const fs = require('fs')
const path = require('path')
//open
fs.open(path.resolve('data.txt'),'r',(err,fd)=>{
console.log(fd);//3 文件操作符
fs.close(fd,err=>{
console.log('关闭成功')
})
})

const fs = require('fs')
//read :所谓的读操作就是将数据从磁盘文件中写入到buffer中
let buf = Buffer.alloc(10)
fs.open('data.txt','r',(err,rfd)=>{
console.log(rfd);
/*
fd 定位当前打开的文件
buf 用于表示当前缓存区
offset 表示当前从buf 的哪个位置开始执行写入
length 表示当前次写入的长度
position 表示当前从文件的哪个位置开始读取
*/
//fs.read(fd:number,buffer:Buffer,offset:number,length:number ,position:number,(err,bytesRead:number,buffer:Buffer)=>void)
//fs.read(rfd,buf,0,3,0,(err,readBytes,data)=>{//readBytes=3
fs.read(rfd,buf,0,4,0,(err,readBytes,data)=>{
console.log(readBytes)
console.log(data)
console.log(data.toString())
})
})
//write 将缓冲区里的内容写入磁盘文件中
buf = Buffer.from('123456789')
fs.open('b.txt','w',(err,wfd)=>{
fs.write(wfd,buf,1,3,0,(err,writen,buffer)=>{
//1 从buffer中的哪个位置开始读取
//3 表示当前写入字节长度
//0 从文件的哪个字节开始写操作
console.log(written);//3 表示当前写入字节长度
console.log(buffer);
console.log(buffer.toString());
fs.close(wfd)
})
})
文件拷贝自定义实现
let buf = Buffer.alloc(10)
//01 打开指定的文件
fs.open('a.txt','r',(err,rfd)=>{
//02 打开的文件读取数据
fs.read(rfd,buf,0,10,0,(err,readBytes)=>{
//03 打开b文件,用于执行数据写入操作
fs.open('b.txt','w',(err,wfd)=>{
//04 将buffer中的数据写入到b.txt当中
fs.write(wfd,buf,0,10,0,(err,written)=>{
console.log('写如成功')
})
})
})
})
//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('写入成功')
})
})
})
})
数据完全拷贝
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 fs = require('fs')
let buf = Buffer.alloc(20)
const BUFFER_SIZE = 10
let readOffset = 0
fs.open('testfile.rar', 'r', (err, rfd) => {
fs.open('testfile1.rar', '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()
})
})
常见目录操作api
- access 判断文件或目录是否具有操作权限
- stat 获取目录及文件信息
- mkdir 创建目录
- rmdir 删除目录
- readdir 读取目录中内容
- unlink 删除指定文件
const fs = require('fs')
fs.access('a.txt',(err)=>{
if(err){//不存在或没有权限
console.log(err)
}else{
console.log('有权限操作')
}
})
fs.stat('a.txt',(err,statObj)=>{
console.log(statObj,size)
console.log(statObj.isFile())
console.log(statObj.isDirectory())
})
fs.mkdir('a/b/c',(err)=>{
if(!err){
console.log('创建成功')
}else{
console.log(err)
}
})
//递归创建
fs.mkdir('a/b/c',{recursive:true},(err)=>{
if(!err){
console.log('创建成功')
}else{
console.log(err)
}
})
fs.rmdir('a/b/c',(err)=>{
if(!err){
console.log('删除成功')
}
})
//递归操作
fs.rmdir('a',{recursive:true},(err)=>{
if(!err){
console.log('删除成功')
}
})
//readdir
fs.readdir('a/b',(err,files)=>{
console.log(files)
})
//unlink
fs.unlink('a/a.txt',(err)=>{
if(!err){
console.log('删除成功')
}
})
同步创建目录
01 将来调用需要接收蕾仕于 a/b/c ,这种路径 他们质检采用/取进行连接
02 利用/ 分隔符将路径进行拆分,将每一项放入一个数组中进行管理['a','b','c']
03 对尚需的数组进行遍历,我们需要拿到每一项,然后与前一项拼接 /
04 判断一个当前对拼接之后的路径是否具有可操作的权限,如果有则证明存在,否则的话就需要执行创建
const path = require('path')
const fs = require('fs')
function makeDirSync(dirPath){
let items = dirPath.split(path.sep);
console.log(path.sep)// \
console.log(items) // [ 'a', 'b', 'c' ]
for(let i = 1;i<=items.length;i++){
let dir = items.slice(0,i).join(path.sep)
try{
fs.accessSync(dir)
}catch(err){
fs.mkdirSync(dir)
}
}
}
makeDirSync('a\\b\\c')
异步创建目录
const fs = require('fs')
const path = require('path')
function mkDir(dirPath,cb){
let parts = dirPath.split('/')
let index = 1
function next(){
if(index>parts.length)return cb && cb()
let current = parts.slice(0,index++).join('/')
fs.access(current,(err)=>{//是否有操作权限
if(err){
fs.mkdir(current,next)
}else{
next()
}
})
}
next();
}
mkDir('a/b/c',()=>{
console.log('创建成功')
})
util工具包
const fs = require('fs')
const path = require('path')
const {promisify} = require('util')
//promisify 包装处理成async 风格
const access = promisify(fs.access)
const access = promisify(fs.mkdir)
async function myMkdir(dirPath,cb){
let parts = dirPath.split('/')
for(let index = 1;index<=parts.length; index++){
let current =parts.slice(0,index).join('/')
try{
await access(current)
}catch(err){
await mkdir(current)
}
}
cb && cb()
}
mkMkdir('a/b/c',()=>{
console.log('创建成功')
})
function myRmdir(dirPath,cb){
fs.stat(dirPath,(err,staObj)=>{
if(statObj.isDirectory()){
fs.readdir(dirPath,(err,files)=>{
let dirs = files.map(item=>{
return path.join(dirPath,item)
})
let index = 0
function next(){
if(index==dirs.length){
return fs.rmdir(dirPath,cb)
}
let current = dirs[index++]
myRmdir(current,next)
}
next()
})
}else{
fs.unlink(dirPath,cb)
}
})
}
系统开发常见问题
- 命名冲突和污染
- 代码冗余,无效请求多
- 文件间的以来关系复杂
常见模块开发规范
- Commonjs规范(nodejs 模块加载都是用的同步)
- 每个模块文件上存在
module,exports,require三个变量module记录当前模块信息。require引入模块的方法。exports当前模块导出的属性- 例如:module.exports =function set(){}
- 每个模块文件上存在
- AMD规范(异步加载模块操作)
define('myModule', ['jquery'], function($) {
// $ 是 jquery 模块的输出
$('body').text('hello world');
});
// 使用
require(['myModule'], function(myModule) {});
- CMD规范(commonjs+amd) 代表cjs
/*
* cmd 模块
*/
define(function(require, exports, module) {
exports.alert = function() {
window.alert("test alert");
};
});
- ES modules 规范(2015)
<script type='module'></script>
//导出
export var name = "foo-module"
export function hello () {
console.log("hello")
}
export class Person {}
//导入
import {引入的成员} from 引入模块地址
模块化规范
- 模块化是前端走向工程化中的重要一环
- 早期javascript 语言层面没有模块化规范
- Commonjs AMD CMD都是模块化规范
- ES6中将模块化纳入标准规范
- 当前常用规范是Commonjs(node下) 与 ESM(前端)
Nodejs与CommonJS
- 任意一个文件就是一个模块,具有独立作用域
- 使用require导入其他模块
- 将模块id导入require实现目标模块定位
module 属性
- 任意js文件就是一个模块,可以直接使用module属性
- id 返回模块标识符,一般是一个绝对路径
- filename 返回文件模块的绝对路径
- loaded 返回布尔值,表示模块是否完成加载
- parent 返回对象存放调用当前模块的模块
- children 返回数组,存放当前模块调用的其他模块
- exports 返回当前模块需要暴露的内容
- paths 返回数组, 存放不同目录下的node_modules位置


require 属性
- 基本功能是读入并执行一个模块文件
- resolve 返回模块文件绝对路径
- extensions 依据不同后缀名执行解析操作
- main 返回主模块对象
CommonJS规范
- CommonJS 规范起初是为了弥补js语言模块化缺陷
- CommonJS是语言层面的规范,当前主要用于nodejs
- CommonJS规定模块化分为引入,定义,标识符三个部分
- Moudle在任意模块中可直接使用包含模块信息
- Require接收标识符,加载目标模块
- Exports与module.exports 都能导出模块数据
- CommonJS规范定义模块的加载是同步完成
Nodejs 与CommonJS
- 使用module.exports与require实现模块导入与导出
- module 属性及其常见信息获取
- exports 导出数据及其与module.exports 区别
- CommonJS规范下的模块同步加载
模块导入与导出
./m.js
const age = 18
const addFn = (x,y)=>{
return x+y
}
//导出
module.exports={
age:age,
addFn:addFn,
}
./main.js
//导入
let obj = require('./m')
console.log(obj);//{age:18,addFn:[Function: addFn]}
//module
//nodejs中每一个js文件都有一个module
./m.js
console.log(module)
//module
let obj = require('./m')
exports.name = 'zce'
let obj = require('./m')
console.log(obj)//{name:"zce"}
exports = {//无法导出
name:'syy',
age:18,
}
//同步加载
//./main1.js
console.log(require.main===module)//true 主入口文件
./m.js
console.log(require.main===module)//false
模块加载速度
- 核心模块 Node源码编译时写入到二进制文件中
- 文件模块 代码运行时, 动态加载
加载流程
- 路径分析 依据标识符确定模块位置
- 文件定位 确定目标模块中具体的文件及文件类型
- 编译执行 采用对应的方式完成文件的编译执行
console.log(module.paths)
文件定位
- 项目存在m1.js模块,导入时使用require('m1')语法
- m1.js->m1.json->m1.node 没有扩展名就按照这个扩展名查找文件
- 查找package.json文件,使用JSON.parse()解析后查找main.js
- main.js->main.json->main.node 查找main文件
- 将index做为目标模块中的具体文件名称
js文件的编译执行
- 使用fs模块同步读入目标文件内容
- 对内容进行语法包装,生成可执行js函数
- 调用函数时传入exports module require等属性值
缓存优化原则
- 提高模块加载速度
- 当前模块不存在,则经历一次完整加载流程
- 模块加载完成后,使用路径做为索引进行缓存
加载流程小结
- 路径分析 确定目标模块位置
- 文件定位 确定目标模块中的具体文件
- 编译执行 对模块内容进行编译 返回可用exports对象
模块分类
- 内置模块
- 文件模块
模块加载速度
- 核心模块 node 源码编译时写入到二进制文件中
- 文件模块 代码运行时 动态加载

vm一个独立的沙箱环境
const fs = require('fs')
const vm = require('vm')
let content = fs.readFileSync('test.txt','utf-8')
console.log(content)
eval(content)
console.log(age)//18
const fs = require('fs')
const vm = require('vm')
let content = fs.readFileSync('test.txt','utf-8')
let age = 23
console.log(age)
let fn = new Function('age','return age+1')
console.log(fn(age))
function Module(id){
this.id = id
this.exports = {}
}
Module._resolveFilename = function(filename){
//利用Path 将filename转为绝对路径
let absPath = path.resolve(__dirname,filename)
console.log(absPath);
}
function myRequire(filename){
//绝对路径
let mPath = Module._resolveFilename(filename)
}
通过 EventEmitter 类实现事件统一管理
events与EventEmitter
- node.js 是基于事件驱动的异步操作架构,内置events模块
- events模块提供了EventEmitter类
- node.js中很多内置核心模块继承EventEmitter
EventEmitter常见API
- on 添加当事件被触发时调用的回调函数
- emit 触发事件 按照注册的序同步调用每个事件监听器
- once 添加当事件在注册之后首次被触发时调用的回调函数
- off 移除特定的监听器
const EventEmitter = require('events')
const ev = new EventEmitter()
//on
ev.on('事件1',()=>{
console.log('事件1执行了')
})
//emit
ev.emit('事件1')
const EventEmitter = require('events')
const ev = new EventEmitter()
//on
ev.on('事件1',()=>{
console.log('事件1执行了--2')
})
ev.on('事件1',()=>{
console.log('事件1执行了')
})
//emit
ev.emit('事件1')
//事件1执行了--2
//事件1执行了
//once
ev.once('事件1',()=>{
console.log('事件1执行了')
})
ev.once('事件1',()=>{
console.log('事件1执行了--2')
})
ev.emit('事件1')
ev.emit('事件1')
//事件1执行了
//事件1执行了--2
let cbFn = ()=>{
console.log('事件1执行')
}
ev.on('事件1',cbFn)
ev.emit('事件1')
ev.off('事件1',cbFn)
ev.emit('事件1')
//事件1执行了
let cbFn = (a,b)=>{
console.log(a)
console.log(b)
}
ev.on('事件1',cbFn)
ev.emit('事件1',1,2)
//1
//2
let cbFn = (...args)=>{
console.log(args)
}
ev.on('事件1',cbFn)
ev.emit('事件1',1,2,3)
//[1,2,3]
ev.on('事件1',function(){
console.log(this)
})
ev.emit('事件1')

发布订阅要素
- 缓存队列 存放订阅者信息
- 具有增加 删除订阅的能力
- 状态改变时通知所有订阅者执行监听
class PubSub{
constructor(){
this._events={}
}
//注册
subscribe(event,callback){
if(this._events[event]){
//如果当前 event 存在 所以我们值需要往后添加当前次监听操作
this._events[event].push(callback)
}else{
//之前没有订阅过次事件
this._events[event] = [callback]
}
}
//发布
publish(event,...args){
const items = this._events[event]
if(items && items.length){
items.forEach(function(){
callback.call(this,...args)
})
}
}
}
let ps = new PubSub()
ps.Subscribe('事件1',()=>{
console.log('事件1执行了');
})
ps.publish('事件1')

完整事件环执行顺序(浏览器)
- 从上至下执行所有的同步代码
- 执行过程中将遇到的宏任务和微任务添加至相应的队列
- 同步代码执行完毕,执行满足条件的微任务回调
- 微任务队列执行完毕后执行所有满足需求的宏任务回调
- 循环事件环操作
- 注意:每执行一个宏任务之后就会立即检查微任务队列


队列说明
- timers 执行setTimeout 与 setInterval回调
- pending callbacks 执行系统操作的回调,例如tcp udp
- idle prepare 只在系统内部进行使用
- poll 执行与i/o相关的回调
- check 执行setImmediate中的回调
- close callbacks 执行close事件的回调
Nodejs 完整事件环
- 执行同步代码 将不同的任务添加至相应队列
- 所有同步代码执行后会去执行满足条件微任务
- 所有微任务代码执行后会执行timer队列中满足的宏任务
- timer中的所有宏任务执行完成后就会依次切换队列
- 注意:在完成队列切换之前会先清空微任务代码
setTimeout(()=>{
console.log('s1')
})
Promise.resolve().then(()=>{
console.log('p1')
})
console.log('start')
process.nexTick(()=>{
console.log('tick')
})
setImmediate(()=>{
console.log('setimmediate')
})
console.log('end');



Node与浏览器事件环不同
- 任务队列数不同
- Nodejs微任务执行时机不同
- 微任务优先级不同
任务队列数
- 浏览器中只有两个任务队列
- Nodejs中有六个事件队列
微任务执行时机
- 二者都会在同步代码执行完毕后执行微任务
- 浏览器平台下每当一个宏任务执行完毕后就清空微任务
- Nodejs平台在事件队列切换时回去清空微队列
微任务优先级
- 浏览器事件环中,微任务放于事件队列,先进先出
- Nodejs中process.nextTick先于promise.then
ls|grep *.js
Nodejs 中的流就是处理流式数据的抽象接口

常见问题
- 同步读取资源文件,用户需要等待数据读取完成
- 资源文件最终一次性加载至内存,开销较大


v8一般提供的内存大小是1个G
流处理数据的优势
- 时间效率:流的分段处理可以同时操作多个数据chunk
- 空间效率 同一时间流无需占用大内存空间
- 使用方便 流配合管理,扩展程序变得简单
流的分类
- Readable 可读流 能实现数据的读取
- Writeable 可写流 能够实现数据的写操作
- Duplex 双工流 能写又能读
- Tranform 转换流 可读可写 还能实现数据转换
Node.js流特点
- Stream 模块实现了四个具体的抽象
- 所有流都继承EventEmitter
const fs = require('fs')
let rs = fs.createReadStream('./test.txt')
let ws = fs.createWriteStream('./test1.txt')
rs.pipe(ws)
const fs = require('fs')
const rs = fs.createReadStream('note.txt')
rs.pipe(process.stdout)
自定义可读流
- 继承stream 里的Readable
- 重写_read方法调用push产出数据
const {Readable} = require('stream')
//定义数组存放数据,模拟底层数据
let source = ['lg','zce','syy']
//自定义类继承 Readable
class MyReadable extends Readable{//创建可读流
constructor(source){
super()
this.source = source
}
_read(){//重写父类的_read 主要对数据添加
let data = this.source.shift()||null
this.push(data);
}
}
//消费数据
let myReadable = new MyReadable(source)
myReadable.on('readable',()=>{
let data = null
while((data = myReadable.read())!=null){//myReadable.read(2)每次读取两个字符
console.log(data.toString())
}
})
//流动模式
myReadable.on('data',(data)=>{
console.log(data.toString())
})
自定义可读流
- 底层数据读取完成之后如何处理
- 消费者如何获取刻度流中的数据

消费数据
- readable事件 当流中存在可读数据时触发
- data事件 当流中数据块传给消费者后触发
可读流总结
- 明确数据生产与消费流程
- 利用api实现自定义的可读流
- 明确数据消费的事件使用
可写流
const fs = require('fs')
//创建一个可读流 生产数据
let rs = fs.createReadStream('note.txt');
//修改字符编码, 便于后续使用
rs.setEncoding('utf-8')
//创建一个可写流 消费数据
let ws = fs.createWriteStream('node2.txt')
//监听事件调用方法完成具体消费
rs.on('data', (chunk) => {
//执行数据写入
ws.write(chunk);
})
自定义可写流
- 继承stream 模块的Writeable
- 重写_write方法, 调用write执行写入
const { Writable } = require('stream')
class MyWriteable extends Writable {
constructor() {
super()
}
_write(chunk, en, done) {
process.stdout.write(chunk.toString() + "-------")
//写完之后在调用done
process.nextTick(done)//同步代码执行后回调(异步操作)
}
}
//创建可写流用于消费
let myWriteable = new MyWriteable()
//执行数据写入
myWriteable.write('XXX', 'utf-8', () => {
console.log('写入成功');
})
//XXX-------写入成功
可写流事件
- pipe 事件 可读流调用pipe() 方法时触发
- unpipe事件 可读流调用unpipe()方法时触发
双工,转换流
Duplex 是双工流 既可以胜场数据也可以消费数据
自定义双工流
- 继承Duplex类
- 重写_read方法,调用push 生产数据
- 重写_write方法,调用write消费数据
let { Readable, Writable, Duplex } = require('stream')
const { isBuffer } = require('util')
//模拟数据
let source = ['a', 'b', 'c']
//自定义双工流
class MyDuplex extends Duplex {
constructor(options) {
super(source, options)
this.source = source
}
_read() {
let data = this.source.shift() || null
this.push(data);
}
_write(chunk, enc, next) {
if (Buffer, isBuffer) {
chunk = chunk.toString()
}
process.stdout.write(chunk + '-----')
process.nextTick(next);
}
}
//实例化
let myDuplex = new MyDuplex(source)
myDuplex.on('data', (chunk) => {
console.log(chunk.toString())
})
myDuplex.write('测试', 'utf-8', () => {
console.log('双工流可写操作')
})
测试-----a
b
c
双工流可写操作
Transform转化流(也是双工流)
自定义转化流
- 继承Transform 类
- 重写_transform方法,调用push和callback
- 重写_flush方法,粗粒剩余数据
let { Transform } = require('stream')
class MyTransform extends Transform {
constructor(options) {
super()
}
_transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase())
callback(null)
}
}
let a = new MyTransform()
a.write('a')
a.write('b')
a.end('c')
a.pipe(process.stdout)
ABC
nodej中的四种流
Readable 可读流
writeable 可写流
Duplex双工流(读写独立)
Transform转换流(读写流是通的可通过管道中间状态做处理)