1. Node基本概念
1. Node是什么?
Node.js是一个基于Chrome V8引擎的JavaScript运行环境(runtime).Node不是一门语言是让js运行在后端的运行时,并且不包括javascript全集因为在服务端中不包含DOM和BOM,Node也提供了一些新的模块例如http,fs模块等。Nodejs 使用了事件驱动、非阻塞式I/O的模型,使其轻量又高效并且Nodejs的包管理器npm,是全球最大的开源库生态系统。事件驱动与非塞IO后面我们会——介绍。到此我们已经对node有了简单的概念。
2. Node解决了哪些问题?
Node在处理高并发I/O密集场景有明显的性能优势
- 高并发是指在同一时间并发访问服务器
- I/O密集指的是文件操作、网络操作、数据库,相对的有CPU密集,CPU密集指的是逻辑处理运算、压缩、
解压、加密、解密
Web主要场景就是接收客户端的请求读取静态资源和渲染界面,所以Node非常适合Web应用的开发。前后分离
3. JS单线程
javascript在最初设计时设计成了单线程为什么不是多线程呢?如果多个线程同时操作DOM那岂不会很混乱?这里所谓的单线程指的是主线程是单线程的,所以在Node中主线程依旧是单线程的。但是Node可以开启多个子线程
优点:
- 单线程特点是节约了内存,并且不需要在切换执行上下文
- 而且单线程不需要管锁的问题.
4. 同步异步和阻塞非阻塞
- 阻塞和非阻塞 针对的是调用方
- 我调用了一个方法之后的状态 fs.readFile
- 同步异步 针对的是被调用方
- 我调用了一个方法 ,这个方法会给我说他是同步的还是异步的
- 异步非阻塞 (我调用了一个方法,这个方法是异步的,我不想要等待这个方法执行完毕)
- http 第一个请求 要计算 100万个数相加 第二个请求来了,需要等待第一个人计算完成
5. Node中的Event Loop
Node官网说明https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/
2. global
node和前端的区别 前端里面有dom bom 服务端中没有widnow,
服务端中有global属性 全局对象 global有很多属性, 当访问global属性时不需要添加global.
因为默认会在global中查找(作用域链)
node中有一个模块化系统,是以文件为单位的,每个文件都是一个模块,模块中的this被更改了{}
- process 进程(重要)
- Buffer 处理二进制文件
- clearInterval / clearTimeout
- setInterval / setTimeout
- setImmediate / clearImmedate 宏任务
- 还要很多没有被枚举出来的属性 可用console.log(global, {showHidden: true})
宏任务:
- script
- ui
- setTiemout
- setInterval
- requestFrameAnimation
- setImmediate
- MessageChannel
- 异步的 click
- ajax ```javascript // 可以用这个属性来判断当前执行的系统环境 win32 darwin console.log(process.platform);
// 1.node.exe 2.node当前执行的文件 (解析用户自己传递的参数) // 执行node文件 node 文件名 a b c d (webpack —mode —config —port —progress) console.log(process.argv); // let args = process.argv.slice(2); // [ ‘—port’, ‘3000’, ‘—color’, ‘red’, ‘—config’, ‘a.js’ ]
// 当用户在哪执行node命令时 就去哪找配置文件 webpack // 当前用户的工作目录 current working directory (这个目录可以更改,用户自己切换即可 ) console.log(process.cwd());
// 当前文件的所在的目录 这个目录是不能手动修改的 console.log(__dirname);
// 环境变量 可以根据环境变量实现不同的功能 // window set key=value mac export key=value 这样设置的环境变量是临时的变量 console.log(process.env.b); let domain = process.env.NODE_ENV === ‘production’? ‘localhost’:’zfpx.com’;
// node中自己实现的微任务 nextTick / queueMicrotask console.log(process.nextTick);
// node中setImmediate 宏任务 不需要传入时间 setImmediate(() => { console.log(‘setImmediate’) setTimeout(() => { // 进入事件环时 setTimeout 有可能没有完成 console.log(‘timeout’) }, 1000); });
<a name="Nhfz8"></a>
## 3. 模块的概念
<a name="DLzEq"></a>
#### 1. 模块化规范
Node中的模块化规范 commonjs规范(node自己实现的), es6Module(import export), umd 统一模块规范 (如果浏览器不支持commonjs requirejs,直接将变量放到window上),amd规范 requirejs cmd规范 seajs<br />目前主流的两个规范commonjs / ESM
<a name="MXGLL"></a>
#### 2. **commonjs规范(模块的概念)**
1. 可以把复杂的代码拆分成小的模块,方便管理代码和维护
1. 每个模块之间的内容都是相互独立的,互不影响的 (解决变量冲突的问题) 单例模式(不能完全解决,命名空间) 使用自执行函数来解决
1. 规范的定义
1. 每个文件都是一个模块
1. 如果你希望模块中的变量被别人使用,可以使用module.exports 导出这个变量
1. 如果另一个模块想使用这个模块导出的结果 需要使用require语法来引用 (同步)
<a name="IuKjZ"></a>
#### **4. 模块的分类**
1. 核心模块/包 , 内置模块
1. 不是自己写的,也不是安装来的是node中自己提供的,可以直接使用
1. require('fs') , path, http, vm, ......
2. 第三方模块
1. 别人写的模块,通过npm install 安装过来的, 不需要有路径
1. require('commander'); webpack,.....
3. 自定义模块
1. 自己写的模块 引用时需要增加路径(相对路径,绝对路径)
1. require('./utils')
<a name="BnTAn"></a>
#### 5. **核心模块**
1. fs(fileSystem处理文件的)
1. fs.readFileSync() 同步读取文件
1. fs.existsSync() 判断路径文件是否存在
1. fs.mkdir() 创建文件夹
1. ...
2. path(处理路径)
1. path.resolve() 默认连接CWD路径再拼接, 如果末尾遇到/ 会回到根目录
1. path.join() 仅仅路径拼接
1. path.extname() 获取扩展名
1. ...
3. vm(虚拟机模块 沙箱环境)
1. vm.runInThisContext() 运行在当前环境栈
4. ...还有很多
<a name="xqTJL"></a>
## 4. commander
```javascript
// 在npm上的模块都需要先安装在使用 (模块内部也提供了几个属性,也可以在模块中直接访问 - 参数)
const program = require('commander');
program.version('1.0.0')
.command('create').action(()=>{
console.log('创建项目')
})
.name('node')
.usage('my-server')
.option('-p,--port <v>', 'set your port')
.option('-c,--config <v>', 'set your config file')
.parse(process.argv); // -- 开头的是key 不带--是值
5. 模板引擎
模板引擎就是传入参数, 根据特定语法转换为真实数据
沙箱: 虚拟机模块, 干净的环境, 跟外界没有任何关联
模板引擎的实现原理: with语法 + 字符串拼接 + new Function/vm.runInThisContext()
模板引擎一般都是操作字符串逻辑(拼接后的字符串代码),
那么如何让字符串执行?
eval 弊端默认会取当前作用域下的变量, 环境污染
var a = 1
eval('console.log(a)') // 1 污染了环境
new Function 可以创建一个沙箱环境,让字符串执行
var a = 1
let fn = new Function('c','b','d',`let a =1;console.log(a)`);
console.log(fn()); // a is not defined
vm 虚拟机模块 可以建立沙箱环境
const vm = require('vm'); // 虚拟机模块 可以创建沙箱环境
var a = 1
vm.runInThisContext(`console.log(a)`); // a is not defined
1. 实现ejs模板引擎
- template.html
定义了name , age , arr 循环
<!--template.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<%=name%> <%=age%>
<%arr.forEach(item=>{%>
<li><%=item%></li>
<%})%>
</body>
</html>
- template.js 模板引擎, with + 字符串拼接 + new Function
可使用ejs模板引擎处理,可自己实现一个简单模板引擎
// 实现自定义的模板引擎
// const ejs = require('ejs'); // 第三方模块
const path = require('path');
const fs = require('fs');
const renderFile = (filePath,obj,cb) =>{
fs.readFile(filePath,'utf8',function (err,html) {
if(err){
return cb(err,html);
}
// arguments[0] 就是匹配到的原字符串 arguments[1] 就是第一个原来括号
html = html.replace(/\{\{([^}]+)\}\}/g,function () { // RegExp.$1
let key = arguments[1].trim();
return '${'+key+'}' // {{name}} => ${name}
});
let head = `let str = '';\r\n with(obj){\r\n`;
head += 'str+=`'
html = html.replace(/\{\%([^%]+)\%\}/g,function () {
return '`\r\n'+arguments[1] + '\r\nstr+=`\r\n'
})
let tail = '`}\r\n return str;'
let fn = new Function('obj',head + html + tail)
cb(err,fn(obj));
});
}
// ejs实现
// ejs.renderFile(path.resolve(__dirname,'my-template.html'),{name:'zf',age:11,arr:[1,2,3]},function (err,data) {
// console.log(data);
// });
// 自己实现
renderFile(path.resolve(__dirname,'my-template.html'),{name:'zf',age:11,arr:[1,2,3]},function (err,data) {
console.log(data);
});
6. 模块断点调试
https://nodejs.org/en/docs/inspector
- 直接在vscode / webstorm中调试
- 可以在chrome中进行调试方案调试node—inspect-brk执行的文件
7. 手写node require方法
- 会默认调用require语法
- Module.prototype.require 模块的原型上有require方法
- Module._load 调用模块的加载方法 最终返回的是module.exports
- Module._resolveFilename 解析文件名 将文件名变成绝对路径 默认尝试添加 .js / .json /.node 策略模式
- Module._cache 默认会判断是否存在缓存
- new Module 创建模块(对象) id ,exports
- 把模块缓存起来,方便下次使用
__ 根据文件名(绝对路径) 创建一个模块 - tryModuleLoad 尝试加载模块 module.load
- module.paths 第三方模块查找的路径
- 获取当前模块的扩展名 根据扩展名调用对应的方法Module._extensions 策略模式
- 获取文件的内容
- 调用module._compile 方法 vm.runInThisContext
- 将用户的内容 包裹到一个函数中 (function (exports, require, module, filename, dirname) {}) ```javascript // 最终返回的是module.exports 用户会给这个module.exports进行赋值 const path = require(‘path’); const fs = require(‘fs’); const vm = require(‘vm’);
function Module(id) { this.id = id; this.exports = {}; } Module.wrap = function (script) { let arr = [ ‘ (function (exports, require, module, filename, dirname) {‘, script, ‘})’ ] return arr.join(‘’) } Module._extensions = { ‘.js’: function(module) { let content = fs.readFileSync(module.id,’utf8’); let fnStr = Module.wrap(content); let fn = vm.runInThisContext(fnStr); let exports = module.exports; let require = myRequire; let filename = module.id; let dirname = path.dirname(module.id); // 这里的this 就是exports对象 fn.call(exports,exports,require,module,filename,dirname); // 用户会给module.exports 赋值 }, ‘.json’: function(module) { let content = fs.readFileSync(module.id,’utf8’); // 读取出来的是字符串 module.exports = JSON.parse(content); } } Module._resolveFilename = function(filepath) { // 根据当前路径实现解析 let filePath = path.resolve(__dirname, filepath); // 判断当前面文件是否存在 let exists = fs.existsSync(filePath); if (exists) return filePath; // 如果存在直接返回路径
// 尝试添加后缀
let keys = Object.keys(Module._extensions);
for (let i = 0; i < keys.length; i++) {
let currentPath = filePath + keys[i];
if (fs.existsSync(currentPath)) { // 尝试添加后缀查找
return currentPath;
}
}
throw new Error('模块不存在')
} Module.prototype.load = function(filename) { // 获取文件的后缀来进行加载 let extname = path.extname(filename); Module._extensionsextname; // 根据对应的后缀名进行加载 } Module.cache = {}; Module._load = function(filepath) { // 将路径转化成绝对路径 let filename = Module._resolveFilename(filepath);
// 获取路径后不要立即创建模块,先看一眼能否找到以前加载过的模块
let cacheModule = Module.cache[filename];
console.log(cacheModule);
if(cacheModule){
return cacheModule.exports; // 直接返回上一次require的结果
}
// 保证每个模块的唯一性,需要通过唯一路径进行查找
let module = new Module(filename); // id,exports对应的就是当前模块的结果
Module.cache[filename] = module
module.load(filename);
return module.exports;
} function myRequire(filepath) { // 根据路径加载这个模块 return Module._load(filepath) }
let a = req(‘./a.js’) console.log(a)
<a name="ZDeZR"></a>
## 8. Node event 手写
观察者模式 会有两个类,观察者会被存到被观察者中,如果被观察者状态变化,会主动通知观察者,调用观察者的更新方法
观察者模式和发布订阅的场景 区分<br />发布订阅模式
```javascript
function EventEmitter (){
this._events = {}; // 默认给EventEmitter准备的
}
EventEmitter.prototype.on = function (eventName,callback) {
if(!this._events) this._events = {};
// 如果不是newListener 那就需要触发newListener的回调
if(eventName !== 'newListener'){
this.emit('newListener',eventName)
}
(this._events[eventName] || (this._events[eventName] = [])).push(callback)
}
EventEmitter.prototype.emit = function (eventName,...args) {
if(this._events && this._events[eventName]){
this._events[eventName].forEach(event =>event(...args));
}
}
EventEmitter.prototype.off = function (eventName,callback) {
// 先找到对应的数组
if(this._events && this._events[eventName]){
// 1.可以使用数组自带的filter方法 直接过滤,找到索引采用splice删除
// 删除时获取once中的l属性和callback 比较,如果相等则删除
this._events[eventName] = this._events[eventName].filter(cb=>(cb != callback)&&(cb.l !== callback))
}
}
EventEmitter.prototype.once = function (eventName,callback) {
const once =(...args)=> {
callback(...args);
this.off(eventName,once)
}
once.l = callback; // 给once增加callback的标识
this.on(eventName,once); // 先绑定一个一次性事件,稍后触发时,再将事件清空
}
module.exports = EventEmitter;
9. npm
概述: npm 是node的包管理器, 管理都是node的模块
1. 什么是3N模块
- nrm node中的源管理工具包 (主要切换npm配置的源registry)
- nvm node中的版本管理工具 (切换node的版本)
- npm 包管理工具, 官网存放着成千上万个不同功能的包供开源使用
2. 第三方模块分类
- 全局模块
- 只能在命令行中使用 任何路径都可以
- 本地模块
- 开发或者上线使用的
- 分为开发依赖 devDependencies, 生产依赖 dependencies
3. 包的初始化工作
npm init -y // -y 是指使用默认配置
npm init // 自定义配置信息
4. 包的管理
// 全局安装模块
npm install nrm -g
nrm的使用 `nrm ls` `nrm use` `nrm current` 再操作配置文件
// 安装到本地 生产依赖 dependencies
npm install vue
// 安装到本地 开发依赖devDependencies (--save-dev || -D)
npm install mockjs -D
5. 编写一个全局包调试
先创建bin的配置
// package.json
{
"name": "my-pack", // 对应的名称
"bin": {
"computed": "./bin/computed.js" // 脚本
}
}
! /usr/bin/env node 以什么方式来运行
#! /usr/bin/env node
const total = process.argv.slice(2).reduce((memo, current) =>
memo += +current
, 0)
console.log(total)
放到npm全局中 (上传后下载-g , 直接临时拷贝过去)
- 上传npm
- 先登录npm addUser
- npm publish发布
// 在my-pack 项目下 运行
npm link
在其他项目npm link my-pack 或者命令行 computed 1 2 3
6. 版本问题
major(破坏性更新).minor(增加功能 修订大版本中的功能).patch(小的bug)
特殊版本
- alpha预览版(内部测试的版本)
- beta(公测版本)
- rc (最终测试版)可上线
版本符号
默认运行npm run 时会将node_modules下的.bin目录放到全局下所有可以使用当前文件夹下的命令
npx 命令 npm 5.2之后提供的(这个命令没有npm run好管理) npx可以去下载包 下载完毕后执行,执行后删除
10. Buffer
node中的fs模块读取文件默认是返回Buffer
// 在node中需要进行文件读取,node中操作的内容默认会存在内存中,
// 内存中的表现形式肯定是二进制的,二进制转换16进制来展现 11111111 ff
const fs = require('fs');
let r = fs.readFileSync('./note.md');
// Buffer可以和字符串直接相互转化
console.log(r.toString());
1. 常用的进制
- 10进制, 2进制, 8进制, 16进制
- 进制的互相转化 js中提供了进制转换的方法 parseInt
- 一个字节是由8bit位(二进制)
- 编码过程中都是以字节为单位 常见的一个字节组成一个字符 双字节组成汉字 三个字节组成一个汉字 (gbk utf8)
- 把任意进制转换成10进制 需要用当前位所在的值 * 当前进制^第几位
把10进制转换成任意进制可以采用取余的方法
// 将任意进制转换成10进制
console.log(parseInt('11',2))
// 可以将任意进制转换成任意进制
console.log((0x16).toString(16))
2. 编码的发展史
了解就好
ASCII 编码 一些常用的符合和字母 进行了一个排号 127 只会占用一个字节大小
- GB2312 用两个字节 27000
- GBK 扩展 4000+
- GB18030 少数民族的汉字
- Unicode 组织
- UTF8 (在utf8编码中 一个字符占用一个字节 一个汉字占用三个字节)
3. blob类型
blob是前端 二进制文件类型,
比如我们input:type=file 读取回来就是一个blob类型
blob的应用
- 前端实现下载功能 前端实现下载 将字符串包装成二进制类型
- 利用new Blob()
- 利用URL.createObjectUrl(blob)
``javascript let str =
hello world`; // 包装后的文件类型不能直接修改 const blob = new Blob([str],{ type:’text/html’ }); const a = document.createElement(‘a’);珠峰
a.setAttribute(‘download’,’index.html’); a.href = URL.createObjectURL(blob); a.click();
2. 前端实现预览功能
1. 读取二进制中内容 fileReader
```javascript
// html=> <input type="file" id="file">
file.addEventListener('change', (e) => {
let file = e.target.files[0]; //二进制文件类型
let fileReader = new FileReader();
fileReader.onload = function() {
let img = document.createElement('img');
img.src = fileReader.result;
document.body.appendChild(img)
}
fileReader.readAsDataURL(file);
})
- 利用URL.createObjectUrl(blob)
// html=> <input type="file" id="file">
file.addEventListener('change', (e) => {
let file = e.target.files[0]; //二进制文件类型
let img = document.createElement('img');
let url = URL.createObjectURL(file);
img.src = url;
document.body.appendChild(img);
// 删除内存中的blob类型Url
// URL.revokeObjectURL(url);
})
4. Buffer的应用
- 服务端可以操作二进制 Buffer 可以和字符串进行相互转化
Buffer代表的都是二进制数据 内存 (buffer不能扩容) java 数组不能扩容 动态数组, 在生成一个新的内存 拷贝过去
buffer的三种声明方式 ```javascript // 开发中数字都是字节为单位 const buffer = Buffer.alloc(5);
// 根据汉字来转化成buffer const buffer1 = Buffer.from(‘珠峰’);
// 通过数组来指定存放的内容 const buffer2 = Buffer.from([0x16,0x32]);
console.log(buffer, ‘buffer ‘) //
2. buffer常用操作
```javascript
// slice
let buf = buffer.slice(0,1); // slice 方法也是“浅拷贝”
// isBuffer
const bool = Buffer.isBuffer(buffer)
// length
const len = buf.length
- buffer方法封装 copy concat
```javascript
Buffer.prototype.copy = function (targetBuffer, targetStart, sourceStart =0 , sourceEnd = this.length) {
for(let i = sourceStart; i < sourceEnd;i++){
} } // buf1.copy(bigBuf,0,0,6) // buf2.copy(bigBuf,6) // console.log(bigBuf.toString())targetBuffer[targetStart++] = this[i];
Buffer.concat = function (bufferList,length= bufferList.reduce((a,b)=>a+b.length,0)) { let buffer = Buffer.alloc(length); let offset = 0; bufferList.forEach(buf=>{ buf.copy(buffer,offset); offset += buf.length; }); return buffer.slice(0,offset); }
const newBuf = Buffer.concat([buf1,buf2,buf1,buf2])
4. 大文件分批上传思路
1. _文件上传 -》 分片上传 =》 “二进制” => buffer 来拼接_
<a name="gndGc"></a>
#### 5. 流 stream
原理: 读取一点写入一点
1. 拷贝文件, 小文件可以使用fs.writeFile / fs.copyFile
1. 如果文件比内存还要大, 那就GG了, 可以采用流的方式读写文件
```javascript
const fs = require('fs')
// 读取默认不指定编码都是buffer类型
let r = fs.readFileSync('./name.txt');
// 默认会将二进制转化成字符串写到文件中 虽然看到的是字符串但是 内部存储的都是二进制
fs.writeFileSync('./age.txt',r);
// 会默认把药拷贝的文件“整个”读取一遍 。
// 特点不能读取比内存大的文件 (会占用很多可用内存)
// stream 边读边写 (采用 分块读取写入的方式 来实现拷贝)
fs.copyFile(source, target)
- 大文件copy
- 使用fs.createReadStream
6. 手写一个可读流方法
原理: 利用发布订阅模式 + fs.open + fs.read + fs.write
// 流 有方向的 读 => 写 node中实现了stream模块
// 文件也想实现流 ,所以内部文件系统集成了stream模块
const path = require('path');
const ReadStream = require('./readStream');
// 创建一个可读流(可读流对象) 这个方法默认并不会读取内容
// fs.open fs.read fs.close
let rs = new ReadStream(path.resolve(__dirname, 'name.txt'), {
flags: 'r', // r代表的是读取
encoding: null, // 默认buffer
mode: 0o666, // 模式 可读可写
autoClose: true, // fs.close
start: 2, // 2 - 8 包前又包后
end: 8,
highWaterMark: 3 // 每次读取的个数 3 3 1
});
// 为了多个异步方法可以解耦, 发布订阅模式
// 可读流继承了events模块,这里的名字必须叫data rs.emit('data'), 如果监听了data 内部会拼命读取文件的内容 ,触发对应的回调
// Buffer.concat()
let bufferArr = []
rs.on('open', (fd) => {
console.log(fd,'---')
})
// 读和写 先读取 =》 像文件中写入 pause() resume()
rs.on('data', (data) => { // 默认会直到文件读取完毕
// rs.pause(); // 让可读流暂停触发data事件
// // console.log('暂停')
// setTimeout(() => {
// rs.resume(); // 再次触发data事件
// }, 1000);
console.log('触发')
bufferArr.push(data);
});
rs.on('end', () => {
console.log(Buffer.concat(bufferArr).toString(),'end');
});
rs.on('error', (err) => {
console.log(err)
});
rs.on('close',()=>{
console.log('close')
})
// on('data') on('end') pause() resume()
// 文件流有两个特殊的事件 ,不是文件流 是普通的流 就没有这两个事件
const EventEmitter = require('events');
const fs = require('fs');
class ReadStream extends EventEmitter {
constructor(path, opts = {}) {
super()
this.path = path;
this.flags = opts.flags || 'r';
this.mode = opts.mode || 0o666;
this.autoClose = opts.autoClose || true;
this.start = opts.start || 0;
this.end = opts.end;
// 读取的数量默认是64k 如果文件大于64k 就可以采用流的方式
this.highWaterMark = opts.highWaterMark || 64 * 1024;
// 记录读取的偏移量
this.pos = this.start;
// 默认创建一个可读流 是非流动模式 不会触发data事件,如果用户监听了data事件后 需要变为流动模式
this.flowing = false; // 是否是流动模式
this.open(); // 打开文件 fs.open
this.on('newListener', (type) => {
if (type === 'data') {
// 用户监听了data
this.flowing = true;
this.read(); // fs.read
}
});
}
open() {
fs.open(this.path, this.flags, this.mode, (err, fd) => {
if (err) {
return this.emit('error', err)
}
this.fd = fd; // 保存到实例上,用于稍后的读取操作
this.emit('open', fd)
})
}
read() {
// 读取必须要等待文件打开完毕, 如果打开了会触发open事件
if (typeof this.fd !== 'number') {
return this.once('open', () => this.read());
}
// 在这之后 文件肯定已经打开了 可以开始进行读取操作
const buffer = Buffer.alloc(this.highWaterMark);
// 3 3 1
// start 0 end 6
// 0 1 2 3 4 5 6
// 0 3
// 3 3
// 6 1
// 每次理论上应该读取highWaterMark个 但是用户能指定了读取的位置
let howMuchToRead = this.end ? Math.min(this.end - this.pos + 1, this.highWaterMark) : this.highWaterMark; // 应该读取几个
fs.read(this.fd,buffer,0,howMuchToRead,this.pos,(err,bytesRead)=>{
if(bytesRead){
this.pos += bytesRead; // 每次读取到后累加
this.emit('data',buffer.slice(0,bytesRead));
if(this.flowing){
this.read();
}
}else{
this.emit('end');
if(this.autoClose){
fs.close(this.fd,()=>{
this.emit('close');
});
}
}
})
}
pause(){
this.flowing = false;
}
resume(){
this.flowing = true;
this.read();
}
}
module.exports = ReadStream;