# notes
**Repository Path**: sunshine-cxh/notes
## Basic Information
- **Project Name**: notes
- **Description**: No description available
- **Primary Language**: JavaScript
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-06-09
- **Last Updated**: 2021-06-09
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Node 个人笔记
## 1. 什么是 Node?
```js
- node不是语言 他是一个让js可以运行在服务端的一个运行时 (内置模块 文件读写 操作系统及的api)
- js语言组成部分 BOM DOM ECMASCRIPT node中只包含了ECMASCRIPT + 模块
- node中间层 解决跨域问题 ssr的实现 工具 (egg nest) 后台项目
- 高并发 (单线程 js中主线程是单线程的)
## 非阻塞异步i/o特性
- 非阻塞 异步 单线程和多线程的区别 i/o
- java 多线程同步
## 单线程 和 多线程
- 多线程可能多条线程操作同一个文件 (锁的问题) 单线程没有锁的问题
- 切换线程执行时 会有消耗 (通过切换时间片的方式 达到同时执行多个任务)
- 多线程占用内存 (可以通过线程池来解决) 也是浪费内存
- 同步阻塞 + 多线程 、 异步非阻塞 + 主线是单线程
- node中自己实现了 异步非阻塞的库 libuv(多线程来实现的) 核心是异步
> 多线程的好处是: 可以做压缩合并 大量计算相关的 (cpu密集型), node适合i/o密集型 (web应用的常见)
## 阻塞非阻塞 、 异步同步
- 我调用一个方法此时我的状态是阻塞还是非阻塞
- 同步阻塞 异步非阻塞
> 当完成任务后会以事件的形式通知我
```
## 2. Node 前的热身
### 2.1 高阶函数
```js
/**
* 什么是高阶函数?
* 满足以下2个条件任意一个就是高阶函数
* 1. 函数的参数是一个函数
* 2. 函数内部返回一个函数
* 高阶函数的应用场景:
* react中利用高阶函数+受控组件收集数据
* 基于原来的代码进行扩展 ==> before
* 自己/第三方封装一些工具类的方法
*/
// before方法 ==> 让所有函数都有这个方法,那么我们在原型上添加这个方法
Function.prototype.before = function(callback) {
// this ==> formerCode ==> 谁调用before就指向谁
// callback ==> before传的函数
// fn ==> before函数返回的新函数
return (...args) => {
callback();
this(...args);
};
};
// 原来的代码 ==> formerCode
function formerCode(...args) {
// 人家的逻辑,我们想在调用这个函数前做一些事情
console.log('我是原来的代码 --- formerCode');
console.log(args);
}
// 定义before函数
const fn = formerCode.before(() => {
console.log('我是在原来代码运行前增加的 --- addCode');
});
fn(1, 2, 3, 4, 5);
```
### 2.2 柯里化函数
```js
/**
* 什么是柯里化函数?
* 将一个函数的功能更具体一点,分批传入参数
* 什么时候用柯里化函数?
* 如果一个函数的参数是固定不变的,那么就使用柯里化函数
*/
// 这是一个求和的函数
function sum(a, b, c, d) {
return a + b + c + d;
}
// 那么我们调用的时候就是这么传参的
// const res = sum(1, 2, 3, 4)
// console.log(res)
// 我们可以实现一个通用的柯里化函数,自动的将函数转成可以多次传递参数
function currie(fn) {
function currfn(args = []) {
// fn.length === sum的形参个数
// args.length === 每次调用传进来的参数
return args.length === fn.length
? fn(...args)
: (...subargs) => currfn([...args, ...subargs]);
}
return currfn();
}
console.log(currie(sum)(1)(2)(3)(4));
```
### 2.3 回调函数解决并发问题
```js
/**
* 什么是回调函数?
* 1. 你自己定义的
* 2. 外界没有手动调用
* 3. 最后自己触发了(达到某一个条件内部触发)
*
* 回调函数无处不在?
* 优点: 可以满足大部分的企业需求
* 缺点: 如果回调函数内部嵌套过多,会造成回调地狱问题
*
*/
// 使用回调函数解决异步并发问题
// 比如node.js中读取文件我们使用异步读取,最后合并所有的打印结果返回,常规打印是同步的,所以打印可能为空
const fs = require('fs');
const path = require('path');
let saveFn = after(2, (obj) => {
console.log(obj);
});
function after(count, callback) {
// 闭包
let obj = {};
return function(key, value) {
obj[key] = value;
if (--count === 0) {
callback(obj);
}
};
}
fs.readFile(path.resolve(__dirname, './name.txt'), (err, data) => {
if (err) {
console.log(err);
return;
}
saveFn('name', data.toString());
});
fs.readFile(path.resolve(__dirname, './age.txt'), (err, data) => {
if (err) {
console.log(err);
return;
}
saveFn('age', data.toString());
});
```
### 2.4 发布订阅模式
```js
/**
* 什么是发布订阅?
* 把需要做的事情放入一个容器(订阅),等这件事情你想做的就把容器中的东西取出来然后去执行(发布)
* 使用场景?
* 一般的像Vue/React中一些通信手段,比如Vuex/Redux都是采用的发布订阅模式来实现的
*/
const fs = require('fs');
const path = require('path');
const person = {};
const events = {
arr: [], // 容器
// 订阅
on(fn) {
this.arr.push(fn);
},
// 发布
emit() {
this.arr.forEach((fn) => fn());
},
};
events.on(() => {
if (Object.keys(person).length === 2) {
console.log(person);
}
});
fs.readFile(path.resolve(__dirname, './name.txt'), (err, data) => {
if (err) {
console.log(err);
return;
}
person.name = data.toString();
events.emit();
});
fs.readFile(path.resolve(__dirname, './age.txt'), (err, data) => {
if (err) {
console.log(err);
return;
}
person.age = data.toString();
events.emit();
});
```
### 2.5 观察者模式
```js
/**
* 什么是观察者模式?
* 观察者模式一般分为二种
* 1. 观察者(内部会有一个方法,一旦被观察者状态发生变化,这个方法会被调用,然后传入被观察者的最新状态)
* 2. 被观察者(内部应该装载着观察者,一旦自己状态改变,应该通知观察者去更新)
*/
// 被观察者
class Observed {
constructor(name) {
this.name = name;
this.status = '难过';
this.arr = []; // 装载容器
}
// 装载函数
attach(observer) {
this.arr.push(observer);
}
// 改变状态的函数
setState(newStatus) {
// 记录旧状态
const oldVal = this.status;
// 改变状态,生成新状态
const newVal = (this.status = newStatus);
// 通知观察者更新
this.arr.forEach((fn) => fn.upload(oldVal, newVal, this.name));
}
}
// 观察者
class Observer {
constructor(name) {
this.name = name;
}
upload(oldVal, newVal, name) {
console.log(`${name}之前${oldVal},${this.name}知道了~~`);
console.log(`${name}现在${newVal},${this.name}知道了~~`);
}
}
const observed = new Observed('宝宝');
const observer1 = new Observer('爸爸');
const observer2 = new Observer('妈妈');
// 装载观察者
observed.attach(observer1);
observed.attach(observer2);
// 更新宝宝的状态
observed.setState('开心');
setTimeout(() => {
observed.setState('睡着了');
}, 2000);
```
### 2.6 手写符合 Promise A+规范的 Promise
```js
function corePromise(p2, x, resolve, reject) {
if (x === p2) {
reject(new TypeError('孩子,别干傻事'));
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
(y) => {
if (called) return;
called = true;
corePromise(p2, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
class Promise {
static all(values) {
return new Promise((resolve, reject) => {
let arr = [],
count = 0;
for (let i = 0; i < values.length; i++) {
const x = values[i];
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
(y) => {
count++;
arr[i] = y;
if (count === values.length) {
resolve(arr);
}
},
(r) => {
reject(r);
}
);
} else {
count++;
arr[i] = x;
}
} else {
count++;
arr[i] = x;
}
}
});
}
static promisify(fn) {
return (...args) => {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
};
}
static resolve(value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(
(y) => {
resolve(y);
},
(r) => {
reject(r);
}
);
} else {
resolve(value);
}
});
}
static reject(value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(reject, reject);
} else {
reject(value);
}
});
}
static race(values) {
return new Promise((resolve, reject) => {
let called = false;
for (let i = 0; i < values.length; i++) {
const x = values[i];
if (x instanceof Promise) {
called = true;
x.then(
(y) => {
resolve(y);
},
(r) => {
reject(r);
}
);
} else {
if (called) return;
// 普通值
resolve(x);
}
}
});
}
constructor(executor) {
this._status = 'pending';
this._value = undefined;
this._callbacks = {
onResolved: [],
onRejected: [],
};
const resolve = (value) => {
if (this._status !== 'pending') return;
this._status = 'resolved';
this._value = value;
this._callbacks.onResolved.forEach((fn) => fn());
};
const reject = (reason) => {
if (this._status !== 'pending') return;
this._status = 'rejected';
this._value = reason;
this._callbacks.onRejected.forEach((fn) => fn());
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : (val) => val;
onRejected =
typeof onRejected === 'function'
? onRejected
: (err) => {
throw err;
};
let p2 = new Promise((resolve, reject) => {
if (this._status === 'resolved') {
setTimeout(() => {
try {
const x = onResolved(this._value);
corePromise(p2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this._status === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this._value);
corePromise(p2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this._status === 'pending') {
this._callbacks.onResolved.push(() => {
setTimeout(() => {
try {
const x = onResolved(this._value);
corePromise(p2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this._callbacks.onRejected.push(() => {
setTimeout(() => {
try {
const x = onRejected(this._value);
corePromise(p2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return p2;
}
catch(onRejected) {
this.then(null, onRejected);
}
finally(fn) {
return this.then(
(y) => fn() || Promise.resolve(y),
(r) => fn() || Promise.reject(r)
);
}
}
module.exports = Promise;
// 测试我们写的Promise是否符合promise a+ 规范
// promise a+ 规范文档地址: https://promisesaplus.com/
// promise a+ 规范测试文档地址: https://github.com/promises-aplus/promises-tests
// 测试流程: yarn --> 下载包 yarn start 跑测试
Promise.deferred = function() {
let dot = {};
// 测试我们的promise,a+规范中只有resolve,reject,并没有其他的一些方法
dot.promise = new Promise((resolve, reject) => {
dot.resolve = resolve; // 测试我们自己实现的resolve
dot.reject = reject; // 测试我们自己实现的reject
});
return dot;
};
module.exports = Promise;
```
### 2.7 Promise 实现红绿灯
```js
Document
```
### 2.8 环境变量
```js
/**
*
* 环境变量:
* 1. 当前代码的可运行环境,这个可运行环境中的变量(process)就叫做环境变量
* 2. 一般分为全局环境变量和临时环境变量
* 3. 全局环境变量: 在电脑中配置死的,比如:window中就在电脑-->高级系统设置-->环境变量
* 4. 临时环境变量: 比如项目中一般都会有这个功能,根据不同的环境变量去读取不同的配置文件,区分开发、生产、测试、预生产
* 5. 设置临时环境变量:
* windows下命令: set xx=xx
* mac下命令: export xx=xx
* 6. 解决不同系统运行指令不一样的问题 --> 第三方插件(cross-env)
*/
// 根据不同的环境加载不同的配置
let prefix = ''
if (process.env.NODE_ENV === 'dev') {
// 开发环境
prefix = '/dev/api'
} else if (process.env.NODE_ENV === 'prod') {
// 生产环境
prefix = '/prod/api'
} else if (process.env.NODE_ENV === 'test') {
// 测试环境
prefix = '/test/api'
} else if (process.env.NODE_ENV === 'bug') {
// 提交bug环境
prefix = '/bug/api'
} else {
// 预发布环境
prefix = '/willProd/api'
}
module.exports = prefix
const config = require('./config')
console.log(config)
`package.json文件中`
{
"name": "varvalid",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "cross-env NODE_ENV=dev node index.js",
"prod": "cross-env NODE_ENV=prod node index.js",
"test": "cross-env NODE_ENV=test node index.js",
"bug": "cross-env NODE_ENV=bug node index.js",
"willProd": "cross-env NODE_ENV=willProd node index.js",
"dev:mac": "export NODE_ENV=dev && node index.js",
"prod:mac": "export NODE_ENV=prod && node index.js",
"test:mac": "export NODE_ENV=test && node index.js",
"bug:mac": "export NODE_ENV=bug && node index.js",
"willProd:mac": "export NODE_ENV=willProd && node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"cross-env": "^7.0.3"
}
}
```
### 2.9 自定义全局包
```js
/**
* 自定义全局包:
* 1. 需要有一个bin目录
* 2. 在bin目录中新建一个文件
* 3. 在该文件的首行指定环境 #! /usr/bin/env node(代表node环境)
* 4. 初始化一个package.json文件
* 5. 在package.json中指定bin字段 "bin": {
"zcf": "./bin/zcf"
},
6. key为指令名字,value为输入该指令,运行的文件
7. 通过npm link 链接(会将该文件临时存放到全局npm目录中)
*/
`zcf文件中`
#! /usr/bin/env node
require('colors');
console.log(`
iiiiiiiiiiiiii iiiiiiiiiiiiii iiiiiiiiiiiiiii
iii ii ii
iii ii iiiiiiiiiiiiiii
iii ii ii
iiiiiiiiiiiiiii iiiiiiiiiiiiii ii
`.cyan);
console.log(`
`)
console.log(`
yyyyyyyyyyyyyyy yy yy yy yy
yy yy yy yy yy
yy yy yyyyyyyyyyyyyy
yy yy yy yy yy
yyyyyyyyyyyyyyy yy yy yy yy
`.cyan)
```
### 3.0 虚拟 dom 渲染成真实 dom
```js
const vDoms = [
{
type: 'h1',
attribute: 'h1',
content: '你好',
children: [
{
type: 'h2',
attribute: 'h2',
content: '你好2',
children: [
{
type: 'h3',
attribute: 'h3',
content: '你好3',
},
],
},
],
},
{
type: 'div',
attribute: 'div',
content: 'div1111',
},
{
type: 'p',
attribute: 'p',
content: 'p1111',
children: [
{
type: 'p',
attribute: 'p1111',
content: 'p儿子1111',
},
{
type: 'p',
attribute: 'p2222',
content: 'p儿子2222',
},
],
},
{
type: 'span',
attribute: 'span',
content: 'span1111',
children: [],
},
];
class renderVDom {
constructor(vDoms) {
// 初始化文档碎片
this.fragment = document.createDocumentFragment();
this.count = 0;
this.vDoms = vDoms;
// 开始渲染vDoms
this.render(vDoms);
}
// 渲染成dom节点
render(vDoms, parentDom) {
// 判断数据类型
if (vDoms.constructor.name !== 'Array') {
console.error('传入数据格式需要为一个数组');
}
for (let i = 0; i < vDoms.length; i++) {
// 每一个虚拟dom对象
const vDom = vDoms[i];
// 解构拿到里面的3个属性
const { type, content, attribute } = vDom;
// 如果type不存在,输出错误
if (!type) {
console.error('类型错误');
}
// 开始编译vDom,第一次的parentDom为undefined
this.compile(vDom, parentDom);
}
}
// 编译成dom
compile(vDom, parentDom) {
const { type, content, attribute } = vDom;
// 判断parentDom是否为undefined,如果为undefiend就代表是一级节点,就应该插入到文档碎片里面,如果不是,就应该插入到父级节点中
const parentEl = parentDom ? parentDom : this.fragment;
// 创建标签
const dom = document.createElement(type);
// 写入标签内容
dom.textContent = content;
// 如果属性存在就设置属性
if (attribute) {
this.setDomAttribute(dom, attribute);
}
// 如果有children属性并且长度大于0
if (vDom.children && vDom.children.length > 0) {
// 递归渲染,这个传的dom就代表是父级了,那parentDom就会有值
this.render(vDom.children, dom);
}
// 如果
if (parentEl === this.fragment) {
this.count++;
}
this.appendFragment(parentEl, dom);
}
// 设置属性
setDomAttribute(dom, attribute) {
dom.setAttribute('class', attribute);
}
// 插入到文档碎片
appendFragment(parentDom, dom) {
parentDom.appendChild(dom);
if (this.count === this.vDoms.length) {
this.appendNode(this.fragment);
}
}
// 把文档碎片插入到指定元素中
appendNode(fragment, node) {
node = node ? node : document.body;
node.appendChild(fragment);
}
}
new renderVDom(vDoms);
```
## 3. EventLoop
```js
默认是先从上到下依次执行代码,
依次清空每个队列中的回调方法.每调用一个宏任务后都会清空微任务;
// 宏任务 (老版本中 是每清空完毕一个队列后才会去执行微任务)
// timers 存放所有定时器回调的 [fn,fn,fn]
// poll阶段 主要存放的异步的i/o操作 node中基本上所有的异步api的回调都会在这个阶段来处理 []
// check是存放setImmediate的回调 []
// 主栈 => 检测时间又没有到达的定时,有就执行 (清空任务) => 下一个阶段就是poll(i/o操作) => 也是逐一清空 => 看setImmediate队列中是否有内容,如果有内容则清空check阶段, 如果没有就在这阻塞 => 不停的看定时器中有没有到达时间,如果有则回去继续执行
```
## 4. Package.json
```js
依赖分为 开发依赖 项目依赖 同版本依赖 捆绑依赖(打包依赖 npm pack) 可选依赖
开发依赖:devDependencies ==> 开发环境需要用到的包
项目依赖:dependencies ==> 生产环境需要用到的包
同版本依赖:peerDependencies ==> 同一个版本需要的依赖
捆绑依赖:bundledDependencies ==> 下某一个包时对应的捆绑依赖,没有下载会提示
打包依赖命令: npm pack
## scripts
scripts中可以配置命令,然后通过npm/yarn来启动命令
## npx
npx 是node5.2之后赠送给你的
npx 直接运行node_modules/.bin文件夹下命令 多了一个下载功能 用完即删除 方便
```
## 5. Commjs 规范
```js
## 为什么需要模块化?
可以解决冲突、实现高内聚低耦合
1.每一个文件都是一个模块
2.需要通过module.exports 导出需要给别人使用的值
3.通过require 拿到需要的结果
## 实现Commjs规范
const path = require('path');
const fs = require('fs');
const vm = require('vm'); // 虚拟机模块 创建沙箱用的
function Module(id) {
this.id = id;
this.exports = {};
}
// 内部可能有n种解析规则
Module._extensions = {
'.js'(module) {
let script = fs.readFileSync(module.id, 'utf8'); // 读取文件的内容
let code = `(function (exports, require, module, __filename, __dirname) {
${script}
})`;
let func = vm.runInThisContext(code);
let exports = module.exports;
let thisValue = exports
let dirname = path.dirname(module.id);
func.call(thisValue,exports,req,module,module.id,dirname);
},
'.json'(module) {
let script = fs.readFileSync(module.id, 'utf8');
module.exports = JSON.parse(script)
} // 根据不同的后缀 定义解析规则
}
Module._resolveFilename = function(id) {
let filePath = path.resolve(__dirname, id);
// 我应该看下这个文件路径是否存在,如果不存在尝试添加后缀
let isExsits = fs.existsSync(filePath);
if (isExsits) return filePath; // 文件存在直接返回
let keys = Object.keys(Module._extensions); // [.js,.json]
for (let i = 0; i < keys.length; i++) {
let newFilePath = filePath + keys[i];
if (fs.existsSync(newFilePath)) return newFilePath
}
throw new Error('模块文件不存在')
}
Module.prototype.load = function() {
// 核心的加载,根据文件不同的后缀名进行加载
let extname = path.extname(this.id);
Module._extensions[extname](this);
}
Module._cache = {};
Module._load = function(id) {
let filename = Module._resolveFilename(id); // 就是将用户的路径变成绝对路径
if(Module._cache[filename]){
return Module._cache[filename].exports; // 如果有缓存直接将上次缓存的结果返回即可
}
let module = new Module(filename);
Module._cache[filename] = module;
module.load(); // 内部会读取文件 用户会给exports对象赋值
return module.exports;
}
function req(id) { // 根据用户名加载模块
return Module._load(id);
}
const r = require('./b.js');
// 基本数据类型和 引用类型的区别
setTimeout(() => {
let r = require('./b.js');
console.log(r);
}, 2000);
console.log(r);
// 调试方法
// node --inspect-brk 文件名
// chrome://inspect/
// vscode 直接进行调试 要配置文件,删除跳过源代码那块
```
## 6. Event 使用
```js
const Events = require('./myEvents');
const event = new Events();
/**
* newListener 是events事件监听器自带的,每次绑定一个事件后都会触发newListener对应的回调函数
*/
// event.on('newListener', (event, listener) => {
// console.log(event, listener)
// })
// event.on('newListener', (event, listener) => {
// console.log(event, listener)
// })
event.on('test', (value) => {
console.log(value);
});
event.once('test2', (...args) => {
console.log('只触发一次', ...args);
});
event.emit('test2', 1231, 111);
event.emit('test2', 2113, 222);
// event.emit('test', '吃')
// event.emit('test', '喝')
// event.emit('test', '玩')
```
### 6.1 手写 Event
```js
// { eventName1: [fn1, fn2] }
class Events {
constructor() {
this.events = {};
}
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [callback];
} else {
this.events[eventName] = [...this.events[eventName], callback];
}
}
emit(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach((fn) => fn(...args));
}
}
once(eventName, callback) {
// 绑定之后被触发一次就解绑
const fn = (...args) => {
callback(...args);
this.off(eventName, fn);
};
this.on(eventName, fn);
}
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter((fn) => {
return fn !== callback;
});
}
}
}
module.exports = Events;
```
## 7. 读取和写入(fs)
```js
// node中fs方法分为2种,同步和异步
// 同步操作会阻塞,我们一般都使用异步的方法
// 异步操作node中很常见,一般都是通过回调函数的方法通知我们,第一个参数一般都是为error
// 优点:不会阻塞,读取速度快,适用于小文件(小于64kb的都属于小文件)
// 缺点:不适用大文件读写操作,,因为是把所有的数据都读完在进行写入的,可能会导致内存溢出
// 解决方式: 可以读取一点写入一点,可以通过 fs.open,fs.read,fs.write
```
### 7.1 普通方式读取和写入
```js
const fs = require('fs');
const path = require('path');
fs.readFile(path.resolve(__dirname, './test.md'), (err, data) => {
if (err) {
return console.log(err);
}
fs.writeFile(path.resolve(__dirname, './copy.md'), data, (err) => {
if (err) {
return console.log(err);
}
console.log('copy success');
});
});
```
### 7.2 读取部分读取和写入部分数据
#### 7.2.1 普通方式
```js
const fs = require('fs');
const path = require('path');
const buffer = Buffer.alloc(3);
fs.open(path.resolve(__dirname, './test.md'), 'r', 0o666, (err, fd) => {
if (err) {
return console.log(err);
}
// console.log(fd) // 读取到的是字节 22
/**
* fd:文件描述符
* buffer:写入哪个buffer
* 0:从buffer的哪个位置开始写
* 3:每次写入几个
* 0:读取文件的位置
*/
fs.read(fd, buffer, 0, 3, 0, (err, bytesRead) => {
if (err) {
return console.log(err);
}
// console.log(bytesRead) // 读取到的是真实字节的个数 一个中文字符代表3个字节
// console.log(buffer, 'buffer') //
fs.open(path.resolve(__dirname, './copy.md'), 'w', (err, wfd) => {
if (err) {
return console.log(err);
}
fs.write(wfd, buffer, (err, written) => {
if (err) {
return console.log(err);
}
console.log(written, 'written'); // 写入字节的个数
// 关闭
fs.close(fd, () => {});
fs.close(wfd, () => {});
});
});
});
});
```
#### 7.2.2 递归写入
```js
const fs = require('fs');
const path = require('path');
let position = 0;
let size = 1;
let buffer = Buffer.alloc(size);
// 可以基于流来实现 大文件的读取
// fs中 createReadStream createWriteStream 基于stream模块来实现的
fs.open(path.resolve(__dirname, './test.md'), 'r', (err, rfd) => {
fs.open(path.resolve(__dirname, './copy.md'), 'w', (err, wfd) => {
function next() {
fs.read(rfd, buffer, 0, size, position, (err, bytesRead) => {
if (bytesRead > 0) {
// 读到了内容
fs.write(wfd, buffer, 0, bytesRead, position, (err, written) => {
// 写入成功,修正下次读取位置
position += bytesRead;
next();
});
} else {
// 读取完毕
fs.close(rfd, () => {});
fs.close(wfd, () => {});
}
});
}
next();
});
});
```
### 7.3 可读流使用
```js
const fs = require('fs');
const path = require('path');
const arr = [];
const rs = fs.createReadStream(path.resolve(__dirname, './test.md'), {
flags: 'r',
encoding: null,
mode: 0o666,
autoClose: true,
start: 0,
highWaterMark: 3, // 每次读取3个字节
});
rs.on('open', (fd) => {
console.log(fd, 'fd');
});
rs.on('data', (chunk) => {
arr.push(chunk);
console.log(chunk, 'chunk');
});
rs.on('end', () => {
console.log('end');
console.log(Buffer.concat(arr).toString());
});
rs.on('close', () => {
console.log('close');
});
```
### 7.4 手写可读流
```js
const fs = require('fs');
const Event = require('events');
class ReadStream extends Event {
// 初始化参数
constructor(path, options) {
super(); // 继承父类Event的方法和属性
this.path = path;
this.encoding = options.encoding || null;
this.fd = options.fd || null;
this.flags = options.flags || 'r';
this.mode = options.mode || 0o666;
this.autoClose = options.autoClose || true;
this.start = options.start || 0;
this.end = options.end || undefined;
this.highWaterMark = options.highWaterMark || 64 * 1024; // 每次读取64kb
this.offset = 0;
this.open(); // 打开文件
// 每次绑定事件都会触发此事件
this.on('newListener', (type) => {
if (type === 'data') {
// 说明外界有绑定消费(data)事件
this.read(); // 读取文件
}
});
}
// 打开文件
open() {
fs.open(this.path, this.flags, (err, fd) => {
if (err) {
this.destroy(err);
return;
}
if (!this.fd) {
this.fd = fd;
}
this.emit('open', fd);
});
}
// 消费
read() {
if (!this.fd) {
this.once('open', () => this.read());
} else {
// 如果外界传了end,就要和highWaterMark进行比较
const readBytes = this.end
? Math.min(this.highWaterMark, this.end - this.offset + 1)
: this.highWaterMark;
const buffer = Buffer.alloc(readBytes);
fs.read(this.fd, buffer, 0, readBytes, this.offset, (err, bytesRead) => {
if (err) {
this.destroy(err);
return;
}
if (bytesRead > 0) {
// 正在读取
this.emit('data', buffer);
// 修正下次读取的偏移量
this.offset += bytesRead;
// 递归读取
this.read();
} else {
// 读完了
this.destroy();
}
});
}
}
// 错误或者关闭
destroy(err) {
if (err) {
// 分发错误事件
this.emit('error', err);
} else {
this.emit('end'); // 分发读取结束事件
if (this.autoClose) {
fs.close(this.fd, () => {
this.emit('close'); // 分发关闭事件
});
}
}
}
}
module.exports = ReadStream;
```
### 7.5 可写流使用
```js
```
### 7.6 手写可写流
```js
```
## 8. 链表
## 9. 树的遍历
## 10. 文件夹递归删除
```js
```
## 11. http 概念
```js
```