Node.js 原理详解

119 阅读4分钟

Buffer

准确的来说,Buffer 是一种计算机中 数据流 结构,计算机中是以二进制的方式,进行数据存取的,而 js 在一开始,没有文件读写能力的,就要借助 Buffer 来实现一些缓冲区的内容。

Buffer 一般用于表示固定长度的缓冲区序列。

Buffer 的声明

let buf1 = Buffer.alloc(5); // 分配 size 个字节的新 Buffer,单位是字节
console.log(buf1); // <Buffer 00 00 00 00 00>

let buf2 = Buffer.from('麓一'); // node 中一般编码使用的是 utf-8, 所以一个汉字,是 3 个字节
console.log(buf2); // <Buffer e9 ba 93 e4 b8 80>

let buf3 = Buffer.from([0xe9, 0xba, 0x93]);
console.log(buf3); // <Buffer e9 ba 93>
console.log(buf3.toString()); // 麓

let buf4 = Buffer.from('a'); // node 一个字母,是 2 个字节
console.log(buf4); // <Buffer 61>

Buffer 和 String 的转换

// String -> Buffer
const buf = Buffer.from('麓一');
// Buffer -> String
const str = buf.toString();

console.log(buf); // <Buffer e9 ba 93 e4 b8 80>
console.log(str); // 麓一

Buffer 的拼接

  • copy
let buf1 = Buffer.from('麓一');
let buf2 = Buffer.from('麓一');

let bigBuffer = Buffer.alloc(6); // 分配 6 个字节

// 第一个 0, 表示从 0 这个位置开始拷贝,第二和第三个数字,表示拷贝从几到几的长度
// buf1.copy(bigBuffer, 0, 0, 3);
// console.log(bigBuffer.toString()); // 麓

buf1.copy(bigBuffer, 0, 0, 2);
buf2.copy(bigBuffer, 2, 2, 6);
console.log(bigBuffer.toString()); // 麓一
  • concat
console.log(Buffer.from('麓一')); // <Buffer e9 ba 93 e4 b8 80>

let buf1 = Buffer.from([0xe9, 0xba]);
console.log(buf1); // <Buffer e9 ba>
let buf2 = Buffer.from([0x93, 0xe4, 0xb8, 0x80]);
console.log(buf2); // <Buffer 93 e4 b8 80>

let bigBuffer = Buffer.concat([buf1, buf2], 6);

console.log(bigBuffer.toString()); // 麓一

Buffer 的截取

let buf1 = Buffer.from('麓一老师');
let buf2 = buf1.slice(0, 6);

console.log(buf1, buf1.toString()); // <Buffer e9 ba 93 e4 b8 80 e8 80 81 e5 b8 88> 麓一老师
console.log(buf2, buf2.toString()); // <Buffer e9 ba 93 e4 b8 80> 麓一

Buffer 的类型判断

let buf1 = Buffer.from('麓一老师');
const isBuffer = Buffer.isBuffer(buf1);

console.log(isBuffer); // true

Buffer 进行文件读写

const fs = require('fs');
const path = require('path');

fs.readFile(path.resolve(__dirname, './package.json'), 'utf-8', function (err, data) {
  if (err) {
    console.log('err >>>> ', err);
    return;
  }
  fs.writeFile(path.resolve(__dirname, './package-copy.json'), data, function (err) {
    if (err) {
      console.log('err >>>> ', err);
      return;
    }
    console.log('写入成功');
  });
});

Stream 流

  • 流的特点:防止淹没可用内存;
  • Buffer 不适合大文件的读取,如果是比较小的文件可以,但是对于大文件,我们就需要使用流;
const buf = Buffer.alloc(3);

// r - read 读
// 777 - 权限
fs.open(path.resolve(__dirname, 'package.json'), 'r', 777, function (err, fd) {
  // fd 文件描述符
  fs.open(path.resolve(__dirname, 'copy.json'), 'w', function (err, wfd) {
    // console.log(fd); // 是一个数字类型,用完需要关闭掉
    function close() {
      fs.close(fd, () => {});
      fs.close(wfd, () => {});
    }
    function next() {
      // buf 写入到哪个 buffer 中,从 buffer 的哪个位置开始写入 3 个字节,从文件的第 0 个位置开始读取
      fs.read(fd, buf, 0, 3, 0, function (err, bytesRead) {
        // bytesRead 实际读取到的个数,这个个数 不一定是 3 ,可能会小于 3
        console.log(bytesRead);

        if (bytesRead == 0) {
          return close();
        }
        // 写入到文件中,从 buffer 的第 0 个位置写入 3 个字节,写入到文件的第 0 个位置
        fs.write(wfd, buf, 0, 3, 0, function (err, bytesWritten) {
          next();
        });
      });
    }
    next();
  });
});

fs 创建可读流

const fs = require('fs');
const path = require('path');

const res = fs.createReadStream(path.resolve(__dirname, './package.json'), {
  flags: 'r', // read
  // start: 0, // 开始位置
  // end: 20, // 结束位置
  highWaterMark: 5, // 默认是 64K
  autoClose: true,
  emitClose: true,
});

let arr = [];

res.on('open', function (fd) {
  console.log('fd >>>> ', fd);
});

res.on('data', function (data) {
  console.log('data >>>> ', data);
  arr.push(data);
});

res.on('end', function () {
  console.log('end >>>> ', Buffer.concat(arr).toString());
});

res.on('close', function () {
  console.log('close');
});

res.on('error', function () {
  console.log('error');
});

/* 
  fd >>>>  4
  data >>>>  <Buffer 7b 0d 0a 20 20>
  data >>>>  <Buffer 22 6e 61 6d 65>
  data >>>>  <Buffer 22 3a 20 22 6e>
  data >>>>  <Buffer 6f 64 65 5f 74>
  data >>>>  <Buffer 68 65 6f 72 79>
  data >>>>  <Buffer 22 2c 0d 0a 20>
  data >>>>  <Buffer 20 22 76 65 72>
  data >>>>  <Buffer 73 69 6f 6e 22>
  data >>>>  <Buffer 3a 20 22 31 2e>
  data >>>>  <Buffer 30 2e 30 22 2c>
  data >>>>  <Buffer 0d 0a 20 20 22>
  data >>>>  <Buffer 6d 61 69 6e 22>
  data >>>>  <Buffer 3a 20 22 69 6e>
  data >>>>  <Buffer 64 65 78 2e 6a>
  data >>>>  <Buffer 73 22 2c 0d 0a>
  data >>>>  <Buffer 20 20 22 6c 69>
  data >>>>  <Buffer 63 65 6e 73 65>
  data >>>>  <Buffer 22 3a 20 22 4d>
  data >>>>  <Buffer 49 54 22 0d 0a>
  data >>>>  <Buffer 7d 0d 0a>
  end >>>>  {
    "name": "node_theory",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT"
  }

  close
*/

文件压缩

const fs = require('fs');
const path = require('path');
const zlib = require('zlib');

fs.createReadStream(path.resolve(__dirname, './package.json'))
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream(path.resolve(__dirname, './package.json.gz')));

Event

// 手写一个发布订阅
function EventEmitter() {
  this._events = {};
}

EventEmitter.prototype.on = function (eventName, callback) {
  if (!this._events) this._events = {};
  let eventCbList = this._events[eventName] || (this._events[eventName] = []);
  eventCbList.push(callback);
};

EventEmitter.prototype.emit = function (eventName, ...rest) {
  this._events[eventName] && this._events[eventName].forEach(cb => cb(...rest));
};

EventEmitter.prototype.off = function (eventName, callback) {
  if (!this._events) this._events = {};
  if (this._events[eventName]) {
    this._events[eventName] = this._events[eventName].filter(
      item => item != callback && item.cb !== callback
    );
  }
};

// 只执行一次
EventEmitter.prototype.once = function (eventName, callback) {
  const once = (...rest) => {
    callback(...rest);
    this.off(eventName, once);
  };

  once.cb = callback;
  this.on(eventName, once);
};

const emitter = new EventEmitter();
// ============ 第一种 =============
// emitter.on('data', function (msg) {
//   console.log('hello' + msg);
// });

// emitter.on('data', function (msg) {
//   console.log('hello2' + msg);
// });

// setTimeout(() => {
//   emitter.emit('data', ' msg');
// }, 500);
/* 
  hello msg
  hello2 msg
*/

// ============ 第二种 =============
// emitter.on('data', function (msg) {
//   console.log('hello' + msg);
// });

// const handle = function (msg) {
//   console.log('hello2' + msg);
// };

// emitter.on('data', handle);

// setTimeout(() => {
//   emitter.emit('data', ' msg');
//   emitter.off('data', handle);
//   emitter.emit('data', ' msg');
// }, 500);

/* 
  hello msg
  hello2 msg
  hello msg
*/

// ============ 第三种 =============
emitter.on('data', function (msg) {
  console.log('hello' + msg);
});

const handle = function (msg) {
  console.log('hello2' + msg);
};

emitter.once('data', handle);

setTimeout(() => {
  emitter.emit('data', ' msg');
  emitter.emit('data', ' msg');
}, 500);

/* 
  hello msg
  hello2 msg
  hello msg
*/

cluster 集群

const http = require('http');

// 集群
const cluster = require('cluster');

// 操作系统
const os = require('os'); // operating system 操作系统

const cpu_num = os.cpus(); // cpu 核数
// console.log('cpu_num ======== ', cpu_num.length);

// 判断进程是否为主进程
if (cluster.isMaster) {
  for (let i = 0; i < cpu_num.length; i++) {
    // 创建一个子进程(衍生新的工作进程,只能从主进程调用)
    cluster.fork();
  }
} else {
  http
    .createServer((req, res) => {
      res.end(`childPid ${process.pid}`);
    })
    .listen(3000);

  console.log(`Worker ${process.pid} started`);
}

child_process 开启子进程

  • ./index.js
const path = require('path');
const http = require('http');

// 子进程,开启子进程
const child_process = require('child_process');

// 操作系统
const os = require('os'); // operating system

const cpu_num = os.cpus();

const server = http
  .createServer((req, res) => {
    res.setHeader('Content-type', 'application/json');
    res.end(JSON.stringify({ pid: process.pid }));
  })
  .listen(3000);

for (let i = 0; i < cpu_num.length - 1; i++) {
  let cp = child_process.fork('server.js', {
    cwd: path.resolve(__dirname),
  });

  cp.send('serverMsg', server, function () {
    console.log('===== send =======');
  });
}
  • ./server.js
const http = require('http');

process.on('message', function (message, server) {
  console.log(message, process.pid, 'child');

  http
    .createServer((req, res) => {
      res.setHeader('Content-type', 'application/json');
      res.end(JSON.stringify({ pid: process.pid }));
    })
    .listen(server);
});

事件循环

浏览器中

一般情况下:

  • 宏任务

    • Input events、keyPress、timers
  • 微任务

    • promise、await async、generator
  • frame

  • resize、scroll、动画、媒体查询器

  • RAF

  • requestAnimationFrame callbacks、IntersectionObserver

  • Layout

  • ResizeObserver

  • Paint

  • 合成

  • RequestIdleCallback

Node 的事件循环

             同步的代码
                 |
     process.nextTick / promise...
                 |
   ┌───────────────────────────┐
┌─>│           timers          │ 定时器: setTimeout / setInterval
│  └─────────────┬─────────────┘
|    process.nextTick / promise...
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │ 执行延迟到下一个循环迭代的 I/O 回调
│  └─────────────┬─────────────┘
|    process.nextTick / promise...
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │ 系统内部使用
│  └─────────────┬─────────────┘      ┌───────────────┐
|    process.nextTick / promise...    │               │
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
|    process.nextTick / promise...    │               │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │ setImmediate
│  └─────────────┬─────────────┘
|    process.nextTick / promise...
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │ 关闭回调函数
   └───────────────────────────┘

  • 定时器:本阶段执行已经被 setTimeout()setInterval() 的调度回调函数;
  • 待定回调:执行延迟到下一个循环迭代的 I/O 回调;
  • idle/prepare:仅系统内部使用;
  • 轮询:检索新的 I/O 事件,执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞;
  • 检测:setImmediate() 回调函数在这里执行;
  • 关闭的回调函数:一些关闭的回调函数,如:socket.on('close', ...)

题目

async function async1() {
  console.log('async1 started');
  await async2();
  console.log('async end'); // m1
}

async function async2() {
  console.log('async2');
}

console.log('script start');

setTimeout(() => {
  console.log('setTimeout0');
  setTimeout(() => {
    console.log('setTimeout1');
  }, 0);
  setImmediate(() => {
    console.log('setImmediate');
  });
}, 0);

async1();

process.nextTick(() => {
  console.log('nextTick'); // m0
});

new Promise(resolve => {
  console.log('promise1');
  resolve();
  console.log('promise2');
}).then(() => {
  console.log('promise.then'); // m2
});

console.log('script end');

/* 
  script start
  async1 started
  async2
  promise1
  promise2
  script end
  nextTick
  async end
  promise.then
  setTimeout0
  setImmediate
  setTimeout1
*/