node.js 原理

61 阅读3分钟

node API

这里提供思路,API自己去找资料

node 的内置对象

Buffer

早期的JS 是没有读取或操作二进制数据流的机制的。 JavaScript 最初被设计用于 HTML 文档的,而文档主要由字符串组成。

浏览器端

ES2015 发布了 typedArray, 更高效的访问和处理二进制。 TypedArray 是给 ArrayBuffer 提供写能力的。

Buffer 的一些概念

Buffer 是计算机中数据流结构,表示的是一个固定长度的缓冲区序列。 比如: 读一个文件

     ------------------

File --> Buffer 缓冲区 wait -> 等待被进程处理 ------------------

Buffer 的 API

声明
const buf1 = Buffer.alloc(5); // 00000000

const buf2 = Buffer.from('麓一'); // utf-8, 一个汉字一般是 3个字节。
// <Buffer e9 ba 93 e4 b8 80>
const buf3 = Buffer.from([0xe4, 0xb8, 0x80]);

console.log(buf1);
console.log(buf2);
console.log(buf3.toString());
拼接
const buf4 = Buffer.from('麓一');

let new_buf = Buffer.alloc(6);
// 
buf4.copy(new_buf, 0, 0, 2);
buf2.copy(new_buf, 2, 2, 6);

console.log(new_buf.toString());

防御型编程。

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

截取
console.log(buf4.slice(3, 6).toString())
类型判断
Buffer.isBuffer(buf);
console.log(Buffer.isBuffer(buf4.toString()));  // false
编解码
base64

3 个 字节。0xff 0xff 0xff 11111111, 11111111, 11111111 二进制 / 24 -> 111111 | 11, 1111 | 1111, 11 | 111111 二进制 / 24 -> 111111 | 111111 | 111111 | 111111 -> 00111111 | 00111111 | 00111111 | 00111111 这个0~63我们用字母表示。 0 ~ 63 如何表示? 0 ~ 25 26 ~ 51 52 ~ 61 62 63 A ~ Z a ~ z 0 ~ 9 + /

// example e9 ba 93 11101001 10111010 10010011 00111010 00011011 00101010 00010011 58, 27, 42, 19 6bqT

文件读写
const fs = require('fs');
const path = require('path');

fs.readFile(path.resolve(__dirname, '../readme.md'), 'utf-8', function(err, data) {
    fs.writeFile(path.resolve(__dirname, '../re.md'), data, function(err) {
        console.log('success');
    })
})
const fs = require('fs');
const path = require('path');

let buf = Buffer.alloc(10);

fs.open(path.resolve(__dirname, './a.js'), 'r', function(err, rfd) {
    fs.read(rfd, buf, 0, 10, 0, function(err, bytesRead) {
        console.log(buf);

        fs.open(path.resolve(__dirname, './b.js'), 'w', 0o666, function(err, wfd) {
            fs.write(wfd, buf, 0, 10, 0, function(err, written) {
                console.log('success');
            })
        })
    })
});

stream

Buffer 在大文件的情况下,内存可能会被淹没。大文件的操作一般用 流 来处理。 FS 很多,很复杂,这里说一说。

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

const res = fs.createReadStream(path.resolve(__dirname, './a.js'), {
    flags: 'r',
    start: 0,
    end: 1000,
    highWaterMark: 20,  //64K
    autoColse: 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');
});
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const res = fs.createReadStream(path.resolve(__dirname, './a.js'))
    .pipe(zlib.createGzip())
    .pipe(fs.createWriteStream(path.resolve(__dirname, './a.js.gz')));

Event

发布订阅 (微内核架构的核心肯定有发布订阅)

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000);
}).then(() => {
    console.log('xxx');
})

为什么我们 .then() 可以执行,因为内部本质上也是有一些发布订阅的内容的。

const e = new EventEmitter();
e.on('test', () => {});  // test方法
e.emit('test');
       |
       V
const e = new EventEmitter();
e.on('test', (params) => {
    console.log(params);
});

e.emit('test', params);

手写时间,浏览器常用的 发布订阅

function EvenetEmitter() {
  this._events = {}
}
EventEmitter.prototype.on = function(eventName, callback) {
   if(!this_events) this._events = {};
   // 这里可能 emit('xx') 多个地方使用emit('text(举个例子)', ()=> {}),所以,先判断有没有值,没有值就给空数组;
   let eventList = this._events[eventName] || (this._events[eventName] = []);
   eventList.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[eventName]) {
      this._events[eventName] = this._event[eventName].filter((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);
}
写到这可以做个测试用例,看能不能跑通

事件循环 v8引擎不实现事件循环,只是执行事件循环。事件循环由宿主决定的 image.png




image.png

宏任务结束了,清空微任务队列时,先执行 nextTick,。。。