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密集场景有明显的性能优势

  1. 高并发是指在同一时间并发访问服务器
  2. I/O密集指的是文件操作、网络操作、数据库,相对的有CPU密集,CPU密集指的是逻辑处理运算、压缩、

解压、加密、解密

Web主要场景就是接收客户端的请求读取静态资源和渲染界面,所以Node非常适合Web应用的开发。前后分离

3. JS单线程

javascript在最初设计时设计成了单线程为什么不是多线程呢?如果多个线程同时操作DOM那岂不会很混乱?这里所谓的单线程指的是主线程是单线程的,所以在Node中主线程依旧是单线程的。但是Node可以开启多个子线程

优点:

  1. 单线程特点是节约了内存,并且不需要在切换执行上下文
  2. 而且单线程不需要管锁的问题.

4. 同步异步和阻塞非阻塞

  1. 阻塞和非阻塞 针对的是调用方
    1. 我调用了一个方法之后的状态 fs.readFile
  2. 同步异步 针对的是被调用方
    1. 我调用了一个方法 ,这个方法会给我说他是同步的还是异步的
  3. 异步非阻塞 (我调用了一个方法,这个方法是异步的,我不想要等待这个方法执行完毕)
    1. http 第一个请求 要计算 100万个数相加 第二个请求来了,需要等待第一个人计算完成

image.png

5. Node中的Event Loop

image.png

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被更改了{}

  1. process 进程(重要)
  2. Buffer 处理二进制文件
  3. clearInterval / clearTimeout
  4. setInterval / setTimeout
  5. setImmediate / clearImmedate 宏任务
  6. 还要很多没有被枚举出来的属性 可用console.log(global, {showHidden: true})


宏任务:

  1. script
  2. ui
  3. setTiemout
  4. setInterval
  5. requestFrameAnimation
  6. setImmediate
  7. MessageChannel
  8. 异步的 click
  9. 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); });

  1. <a name="Nhfz8"></a>
  2. ## 3. 模块的概念
  3. <a name="DLzEq"></a>
  4. #### 1. 模块化规范
  5. Node中的模块化规范 commonjs规范(node自己实现的), es6Module(import export), umd 统一模块规范 (如果浏览器不支持commonjs requirejs,直接将变量放到window上),amd规范 requirejs cmd规范 seajs<br />目前主流的两个规范commonjs / ESM
  6. <a name="MXGLL"></a>
  7. #### 2. **commonjs规范(模块的概念)**
  8. 1. 可以把复杂的代码拆分成小的模块,方便管理代码和维护
  9. 1. 每个模块之间的内容都是相互独立的,互不影响的 (解决变量冲突的问题) 单例模式(不能完全解决,命名空间) 使用自执行函数来解决
  10. 1. 规范的定义
  11. 1. 每个文件都是一个模块
  12. 1. 如果你希望模块中的变量被别人使用,可以使用module.exports 导出这个变量
  13. 1. 如果另一个模块想使用这个模块导出的结果 需要使用require语法来引用 (同步)
  14. <a name="IuKjZ"></a>
  15. #### **4. 模块的分类**
  16. 1. 核心模块/包 , 内置模块
  17. 1. 不是自己写的,也不是安装来的是node中自己提供的,可以直接使用
  18. 1. require('fs') , path, http, vm, ......
  19. 2. 第三方模块
  20. 1. 别人写的模块,通过npm install 安装过来的, 不需要有路径
  21. 1. require('commander'); webpack,.....
  22. 3. 自定义模块
  23. 1. 自己写的模块 引用时需要增加路径(相对路径,绝对路径)
  24. 1. require('./utils')
  25. <a name="BnTAn"></a>
  26. #### 5. **核心模块**
  27. 1. fs(fileSystem处理文件的)
  28. 1. fs.readFileSync() 同步读取文件
  29. 1. fs.existsSync() 判断路径文件是否存在
  30. 1. fs.mkdir() 创建文件夹
  31. 1. ...
  32. 2. path(处理路径)
  33. 1. path.resolve() 默认连接CWD路径再拼接, 如果末尾遇到/ 会回到根目录
  34. 1. path.join() 仅仅路径拼接
  35. 1. path.extname() 获取扩展名
  36. 1. ...
  37. 3. vm(虚拟机模块 沙箱环境)
  38. 1. vm.runInThisContext() 运行在当前环境栈
  39. 4. ...还有很多
  40. <a name="xqTJL"></a>
  41. ## 4. commander
  42. ```javascript
  43. // 在npm上的模块都需要先安装在使用 (模块内部也提供了几个属性,也可以在模块中直接访问 - 参数)
  44. const program = require('commander');
  45. program.version('1.0.0')
  46. .command('create').action(()=>{
  47. console.log('创建项目')
  48. })
  49. .name('node')
  50. .usage('my-server')
  51. .option('-p,--port <v>', 'set your port')
  52. .option('-c,--config <v>', 'set your config file')
  53. .parse(process.argv); // -- 开头的是key 不带--是值

5. 模板引擎

模板引擎就是传入参数, 根据特定语法转换为真实数据
沙箱: 虚拟机模块, 干净的环境, 跟外界没有任何关联

模板引擎的实现原理: with语法 + 字符串拼接 + new Function/vm.runInThisContext()

模板引擎一般都是操作字符串逻辑(拼接后的字符串代码),
那么如何让字符串执行?

  1. eval 弊端默认会取当前作用域下的变量, 环境污染

    1. var a = 1
    2. eval('console.log(a)') // 1 污染了环境
  2. new Function 可以创建一个沙箱环境,让字符串执行

    1. var a = 1
    2. let fn = new Function('c','b','d',`let a =1;console.log(a)`);
    3. console.log(fn()); // a is not defined
  3. vm 虚拟机模块 可以建立沙箱环境

    1. const vm = require('vm'); // 虚拟机模块 可以创建沙箱环境
    2. var a = 1
    3. vm.runInThisContext(`console.log(a)`); // a is not defined

1. 实现ejs模板引擎

  1. template.html

定义了name , age , arr 循环

  1. <!--template.html-->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <%=name%> <%=age%>
  11. <%arr.forEach(item=>{%>
  12. <li><%=item%></li>
  13. <%})%>
  14. </body>
  15. </html>
  1. template.js 模板引擎, with + 字符串拼接 + new Function

可使用ejs模板引擎处理,可自己实现一个简单模板引擎

  1. // 实现自定义的模板引擎
  2. // const ejs = require('ejs'); // 第三方模块
  3. const path = require('path');
  4. const fs = require('fs');
  5. const renderFile = (filePath,obj,cb) =>{
  6. fs.readFile(filePath,'utf8',function (err,html) {
  7. if(err){
  8. return cb(err,html);
  9. }
  10. // arguments[0] 就是匹配到的原字符串 arguments[1] 就是第一个原来括号
  11. html = html.replace(/\{\{([^}]+)\}\}/g,function () { // RegExp.$1
  12. let key = arguments[1].trim();
  13. return '${'+key+'}' // {{name}} => ${name}
  14. });
  15. let head = `let str = '';\r\n with(obj){\r\n`;
  16. head += 'str+=`'
  17. html = html.replace(/\{\%([^%]+)\%\}/g,function () {
  18. return '`\r\n'+arguments[1] + '\r\nstr+=`\r\n'
  19. })
  20. let tail = '`}\r\n return str;'
  21. let fn = new Function('obj',head + html + tail)
  22. cb(err,fn(obj));
  23. });
  24. }
  25. // ejs实现
  26. // ejs.renderFile(path.resolve(__dirname,'my-template.html'),{name:'zf',age:11,arr:[1,2,3]},function (err,data) {
  27. // console.log(data);
  28. // });
  29. // 自己实现
  30. renderFile(path.resolve(__dirname,'my-template.html'),{name:'zf',age:11,arr:[1,2,3]},function (err,data) {
  31. console.log(data);
  32. });

6. 模块断点调试

https://nodejs.org/en/docs/inspector

  1. 直接在vscode / webstorm中调试
  2. 可以在chrome中进行调试方案调试node—inspect-brk执行的文件


7. 手写node require方法

  1. 会默认调用require语法
  2. Module.prototype.require 模块的原型上有require方法
  3. Module._load 调用模块的加载方法 最终返回的是module.exports
  4. Module._resolveFilename 解析文件名 将文件名变成绝对路径 默认尝试添加 .js / .json /.node 策略模式
  5. Module._cache 默认会判断是否存在缓存
  6. new Module 创建模块(对象) id ,exports
  7. 把模块缓存起来,方便下次使用
    __ 根据文件名(绝对路径) 创建一个模块
  8. tryModuleLoad 尝试加载模块 module.load
  9. module.paths 第三方模块查找的路径
  10. 获取当前模块的扩展名 根据扩展名调用对应的方法Module._extensions 策略模式
  11. 获取文件的内容
  12. 调用module._compile 方法 vm.runInThisContext
  13. 将用户的内容 包裹到一个函数中 (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; // 如果存在直接返回路径

  1. // 尝试添加后缀
  2. let keys = Object.keys(Module._extensions);
  3. for (let i = 0; i < keys.length; i++) {
  4. let currentPath = filePath + keys[i];
  5. if (fs.existsSync(currentPath)) { // 尝试添加后缀查找
  6. return currentPath;
  7. }
  8. }
  9. 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);

  1. // 获取路径后不要立即创建模块,先看一眼能否找到以前加载过的模块
  2. let cacheModule = Module.cache[filename];
  3. console.log(cacheModule);
  4. if(cacheModule){
  5. return cacheModule.exports; // 直接返回上一次require的结果
  6. }
  7. // 保证每个模块的唯一性,需要通过唯一路径进行查找
  8. let module = new Module(filename); // id,exports对应的就是当前模块的结果
  9. Module.cache[filename] = module
  10. module.load(filename);
  11. return module.exports;

} function myRequire(filepath) { // 根据路径加载这个模块 return Module._load(filepath) }

let a = req(‘./a.js’) console.log(a)

  1. <a name="ZDeZR"></a>
  2. ## 8. Node event 手写
  3. 观察者模式 会有两个类,观察者会被存到被观察者中,如果被观察者状态变化,会主动通知观察者,调用观察者的更新方法
  4. 观察者模式和发布订阅的场景 区分<br />发布订阅模式
  5. ```javascript
  6. function EventEmitter (){
  7. this._events = {}; // 默认给EventEmitter准备的
  8. }
  9. EventEmitter.prototype.on = function (eventName,callback) {
  10. if(!this._events) this._events = {};
  11. // 如果不是newListener 那就需要触发newListener的回调
  12. if(eventName !== 'newListener'){
  13. this.emit('newListener',eventName)
  14. }
  15. (this._events[eventName] || (this._events[eventName] = [])).push(callback)
  16. }
  17. EventEmitter.prototype.emit = function (eventName,...args) {
  18. if(this._events && this._events[eventName]){
  19. this._events[eventName].forEach(event =>event(...args));
  20. }
  21. }
  22. EventEmitter.prototype.off = function (eventName,callback) {
  23. // 先找到对应的数组
  24. if(this._events && this._events[eventName]){
  25. // 1.可以使用数组自带的filter方法 直接过滤,找到索引采用splice删除
  26. // 删除时获取once中的l属性和callback 比较,如果相等则删除
  27. this._events[eventName] = this._events[eventName].filter(cb=>(cb != callback)&&(cb.l !== callback))
  28. }
  29. }
  30. EventEmitter.prototype.once = function (eventName,callback) {
  31. const once =(...args)=> {
  32. callback(...args);
  33. this.off(eventName,once)
  34. }
  35. once.l = callback; // 给once增加callback的标识
  36. this.on(eventName,once); // 先绑定一个一次性事件,稍后触发时,再将事件清空
  37. }
  38. module.exports = EventEmitter;

9. npm

概述: npm 是node的包管理器, 管理都是node的模块

1. 什么是3N模块

  1. nrm node中的源管理工具包 (主要切换npm配置的源registry)
  2. nvm node中的版本管理工具 (切换node的版本)
  3. npm 包管理工具, 官网存放着成千上万个不同功能的包供开源使用

2. 第三方模块分类

  1. 全局模块
    1. 只能在命令行中使用 任何路径都可以
  2. 本地模块
    1. 开发或者上线使用的
    2. 分为开发依赖 devDependencies, 生产依赖 dependencies

3. 包的初始化工作

  1. npm init -y // -y 是指使用默认配置
  2. npm init // 自定义配置信息

4. 包的管理

  1. // 全局安装模块
  2. npm install nrm -g
  3. nrm的使用 `nrm ls` `nrm use` `nrm current` 再操作配置文件
  4. // 安装到本地 生产依赖 dependencies
  5. npm install vue
  6. // 安装到本地 开发依赖devDependencies (--save-dev || -D)
  7. npm install mockjs -D

5. 编写一个全局包调试

  1. 先创建bin的配置

    1. // package.json
    2. {
    3. "name": "my-pack", // 对应的名称
    4. "bin": {
    5. "computed": "./bin/computed.js" // 脚本
    6. }
    7. }
  2. ! /usr/bin/env node 以什么方式来运行

    1. #! /usr/bin/env node
    2. const total = process.argv.slice(2).reduce((memo, current) =>
    3. memo += +current
    4. , 0)
    5. console.log(total)
  3. 放到npm全局中 (上传后下载-g , 直接临时拷贝过去)

    1. 上传npm
    2. 先登录npm addUser
    3. npm publish发布
      1. // my-pack 项目下 运行
      2. npm link
  4. 在其他项目npm link my-pack 或者命令行 computed 1 2 3

6. 版本问题

major(破坏性更新).minor(增加功能 修订大版本中的功能).patch(小的bug)
特殊版本

  1. alpha预览版(内部测试的版本)
  2. beta(公测版本)
  3. rc (最终测试版)可上线

版本符号
image.png
默认运行npm run 时会将node_modules下的.bin目录放到全局下所有可以使用当前文件夹下的命令
npx 命令 npm 5.2之后提供的(这个命令没有npm run好管理) npx可以去下载包 下载完毕后执行,执行后删除

10. Buffer

node中的fs模块读取文件默认是返回Buffer

  1. // 在node中需要进行文件读取,node中操作的内容默认会存在内存中,
  2. // 内存中的表现形式肯定是二进制的,二进制转换16进制来展现 11111111 ff
  3. const fs = require('fs');
  4. let r = fs.readFileSync('./note.md');
  5. // Buffer可以和字符串直接相互转化
  6. console.log(r.toString());

1. 常用的进制

  1. 10进制, 2进制, 8进制, 16进制
  2. 进制的互相转化 js中提供了进制转换的方法 parseInt
  3. 一个字节是由8bit位(二进制)
  4. 编码过程中都是以字节为单位 常见的一个字节组成一个字符 双字节组成汉字 三个字节组成一个汉字 (gbk utf8)
  5. 把任意进制转换成10进制 需要用当前位所在的值 * 当前进制^第几位
  6. 把10进制转换成任意进制可以采用取余的方法

    1. // 将任意进制转换成10进制
    2. console.log(parseInt('11',2))
    3. // 可以将任意进制转换成任意进制
    4. console.log((0x16).toString(16))

    2. 编码的发展史

    了解就好

  7. ASCII 编码 一些常用的符合和字母 进行了一个排号 127 只会占用一个字节大小

  8. GB2312 用两个字节 27000
  9. GBK 扩展 4000+
  10. GB18030 少数民族的汉字
  11. Unicode 组织
  12. UTF8 (在utf8编码中 一个字符占用一个字节 一个汉字占用三个字节)

3. blob类型

blob是前端 二进制文件类型,
比如我们input:type=file 读取回来就是一个blob类型

blob的应用

  1. 前端实现下载功能 前端实现下载 将字符串包装成二进制类型
    1. 利用new Blob()
    2. 利用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();

  1. 2. 前端实现预览功能
  2. 1. 读取二进制中内容 fileReader
  3. ```javascript
  4. // html=> <input type="file" id="file">
  5. file.addEventListener('change', (e) => {
  6. let file = e.target.files[0]; //二进制文件类型
  7. let fileReader = new FileReader();
  8. fileReader.onload = function() {
  9. let img = document.createElement('img');
  10. img.src = fileReader.result;
  11. document.body.appendChild(img)
  12. }
  13. fileReader.readAsDataURL(file);
  14. })
  1. 利用URL.createObjectUrl(blob)
    1. // html=> <input type="file" id="file">
    2. file.addEventListener('change', (e) => {
    3. let file = e.target.files[0]; //二进制文件类型
    4. let img = document.createElement('img');
    5. let url = URL.createObjectURL(file);
    6. img.src = url;
    7. document.body.appendChild(img);
    8. // 删除内存中的blob类型Url
    9. // URL.revokeObjectURL(url);
    10. })

    4. Buffer的应用

  1. 服务端可以操作二进制 Buffer 可以和字符串进行相互转化
  2. Buffer代表的都是二进制数据 内存 (buffer不能扩容) java 数组不能扩容 动态数组, 在生成一个新的内存 拷贝过去

  3. buffer的三种声明方式 ```javascript // 开发中数字都是字节为单位 const buffer = Buffer.alloc(5);

// 根据汉字来转化成buffer const buffer1 = Buffer.from(‘珠峰’);

// 通过数组来指定存放的内容 const buffer2 = Buffer.from([0x16,0x32]);

console.log(buffer, ‘buffer ‘) // console.log(buffer1, ‘buffer1 ‘) // console.log(buffer2, ‘buffer2 ‘) //

  1. 2. buffer常用操作
  2. ```javascript
  3. // slice
  4. let buf = buffer.slice(0,1); // slice 方法也是“浅拷贝”
  5. // isBuffer
  6. const bool = Buffer.isBuffer(buffer)
  7. // length
  8. const len = buf.length
  1. buffer方法封装 copy concat ```javascript Buffer.prototype.copy = function (targetBuffer, targetStart, sourceStart =0 , sourceEnd = this.length) { for(let i = sourceStart; i < sourceEnd;i++){
    1. targetBuffer[targetStart++] = this[i];
    } } // buf1.copy(bigBuf,0,0,6) // buf2.copy(bigBuf,6) // console.log(bigBuf.toString())

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])

  1. 4. 大文件分批上传思路
  2. 1. _文件上传 -》 分片上传 =》 “二进制” => buffer 来拼接_
  3. <a name="gndGc"></a>
  4. #### 5. 流 stream
  5. 原理: 读取一点写入一点
  6. 1. 拷贝文件, 小文件可以使用fs.writeFile / fs.copyFile
  7. 1. 如果文件比内存还要大, 那就GG了, 可以采用流的方式读写文件
  8. ```javascript
  9. const fs = require('fs')
  10. // 读取默认不指定编码都是buffer类型
  11. let r = fs.readFileSync('./name.txt');
  12. // 默认会将二进制转化成字符串写到文件中 虽然看到的是字符串但是 内部存储的都是二进制
  13. fs.writeFileSync('./age.txt',r);
  14. // 会默认把药拷贝的文件“整个”读取一遍 。
  15. // 特点不能读取比内存大的文件 (会占用很多可用内存)
  16. // stream 边读边写 (采用 分块读取写入的方式 来实现拷贝)
  17. fs.copyFile(source, target)
  1. 大文件copy
    1. 使用fs.createReadStream

6. 手写一个可读流方法

原理: 利用发布订阅模式 + fs.open + fs.read + fs.write

  1. // 流 有方向的 读 => 写 node中实现了stream模块
  2. // 文件也想实现流 ,所以内部文件系统集成了stream模块
  3. const path = require('path');
  4. const ReadStream = require('./readStream');
  5. // 创建一个可读流(可读流对象) 这个方法默认并不会读取内容
  6. // fs.open fs.read fs.close
  7. let rs = new ReadStream(path.resolve(__dirname, 'name.txt'), {
  8. flags: 'r', // r代表的是读取
  9. encoding: null, // 默认buffer
  10. mode: 0o666, // 模式 可读可写
  11. autoClose: true, // fs.close
  12. start: 2, // 2 - 8 包前又包后
  13. end: 8,
  14. highWaterMark: 3 // 每次读取的个数 3 3 1
  15. });
  16. // 为了多个异步方法可以解耦, 发布订阅模式
  17. // 可读流继承了events模块,这里的名字必须叫data rs.emit('data'), 如果监听了data 内部会拼命读取文件的内容 ,触发对应的回调
  18. // Buffer.concat()
  19. let bufferArr = []
  20. rs.on('open', (fd) => {
  21. console.log(fd,'---')
  22. })
  23. // 读和写 先读取 =》 像文件中写入 pause() resume()
  24. rs.on('data', (data) => { // 默认会直到文件读取完毕
  25. // rs.pause(); // 让可读流暂停触发data事件
  26. // // console.log('暂停')
  27. // setTimeout(() => {
  28. // rs.resume(); // 再次触发data事件
  29. // }, 1000);
  30. console.log('触发')
  31. bufferArr.push(data);
  32. });
  33. rs.on('end', () => {
  34. console.log(Buffer.concat(bufferArr).toString(),'end');
  35. });
  36. rs.on('error', (err) => {
  37. console.log(err)
  38. });
  39. rs.on('close',()=>{
  40. console.log('close')
  41. })
  42. // on('data') on('end') pause() resume()
  43. // 文件流有两个特殊的事件 ,不是文件流 是普通的流 就没有这两个事件
  1. const EventEmitter = require('events');
  2. const fs = require('fs');
  3. class ReadStream extends EventEmitter {
  4. constructor(path, opts = {}) {
  5. super()
  6. this.path = path;
  7. this.flags = opts.flags || 'r';
  8. this.mode = opts.mode || 0o666;
  9. this.autoClose = opts.autoClose || true;
  10. this.start = opts.start || 0;
  11. this.end = opts.end;
  12. // 读取的数量默认是64k 如果文件大于64k 就可以采用流的方式
  13. this.highWaterMark = opts.highWaterMark || 64 * 1024;
  14. // 记录读取的偏移量
  15. this.pos = this.start;
  16. // 默认创建一个可读流 是非流动模式 不会触发data事件,如果用户监听了data事件后 需要变为流动模式
  17. this.flowing = false; // 是否是流动模式
  18. this.open(); // 打开文件 fs.open
  19. this.on('newListener', (type) => {
  20. if (type === 'data') {
  21. // 用户监听了data
  22. this.flowing = true;
  23. this.read(); // fs.read
  24. }
  25. });
  26. }
  27. open() {
  28. fs.open(this.path, this.flags, this.mode, (err, fd) => {
  29. if (err) {
  30. return this.emit('error', err)
  31. }
  32. this.fd = fd; // 保存到实例上,用于稍后的读取操作
  33. this.emit('open', fd)
  34. })
  35. }
  36. read() {
  37. // 读取必须要等待文件打开完毕, 如果打开了会触发open事件
  38. if (typeof this.fd !== 'number') {
  39. return this.once('open', () => this.read());
  40. }
  41. // 在这之后 文件肯定已经打开了 可以开始进行读取操作
  42. const buffer = Buffer.alloc(this.highWaterMark);
  43. // 3 3 1
  44. // start 0 end 6
  45. // 0 1 2 3 4 5 6
  46. // 0 3
  47. // 3 3
  48. // 6 1
  49. // 每次理论上应该读取highWaterMark个 但是用户能指定了读取的位置
  50. let howMuchToRead = this.end ? Math.min(this.end - this.pos + 1, this.highWaterMark) : this.highWaterMark; // 应该读取几个
  51. fs.read(this.fd,buffer,0,howMuchToRead,this.pos,(err,bytesRead)=>{
  52. if(bytesRead){
  53. this.pos += bytesRead; // 每次读取到后累加
  54. this.emit('data',buffer.slice(0,bytesRead));
  55. if(this.flowing){
  56. this.read();
  57. }
  58. }else{
  59. this.emit('end');
  60. if(this.autoClose){
  61. fs.close(this.fd,()=>{
  62. this.emit('close');
  63. });
  64. }
  65. }
  66. })
  67. }
  68. pause(){
  69. this.flowing = false;
  70. }
  71. resume(){
  72. this.flowing = true;
  73. this.read();
  74. }
  75. }
  76. module.exports = ReadStream;

11. 单向链表

  1. 单向链表.png单向循环链表.png
  2. 反向链表
  3. 非递归实现翻转

    12. 树

  4. 二叉树

  5. 先中后序遍历