skip to content
天真笔录

node原理

/ 5 min read

Node原理

require的原理

模拟实现require

// myRequire.js
const fs = require("fs");
const path = require("path");
const { Script } = require("vm");

const myRequire = function (filename) {
	const file = fs.readFileSync(path.join(__dirname, filename), { encoding: "utf-8" });
	/*
	 * 读取到的file文件添加到函数中
	 * @require file 中可以使用require
	 * @module file 中可以使用module.exports导出
	 * @exports file 中可以使用exports导出
	 */
	const content = `(function(require, module, exports){
    ${file}
  })`;
	const res = new Script(content).runInThisContext(); // 执行content

	const module = {
		exports: {},
	};

	res(myRequire, module, module.exports);
	return module.exports;
};

module.exports = myRequire;

使用测试

// add.js
console.log("-----require", require); // 此时的require已经是我们自定义的myRequire函数
const add = function (a, b) {
	return a + b;
};

module.exports = {
	add,
};
// main.js
const myRequire = require("./myRequire");

const { add } = myRequire("./add.js");
add(1, 2); // 3

Buffer

const buf1 = Buffer.alloc(5); // 新建buffer期望长度
const buf2 = Buffer.from("天真"); // 传入的buffer数据拷贝到新建的Buffer实例
console.log("buf2==================", buf2);
const buf3 = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); // 八位字节array创建新Buffer
console.log("buf3==================", buf3.toString()); // buffer

// 拼接
let newBuf = Buffer.alloc(6);
buf2.copy(newBuf, 0, 0, 6);
console.log("newBuf==================", newBuf.toString()); // 天真

readFile 读写文件

readFile(path.resolve(__dirname, "./a.js"), "utf-8", function (err, data) {
	writeFile(path.resolve(__dirname, "./a_copy.js"), data, function (error) {
		console.log("error==================", error);
	});
});

Buffer读写文件

// Buffer
let buf = Buffer.alloc(30);
open(path.resolve(__dirname, "./a.js"), "r", function (err, rfd) {
	read(rfd, buf, 0, 30, 0, function (err, bytesRead) {
		console.log("buf==================", buf.toString());

		open(path.resolve(__dirname, "./b.js"), "w", 0o666, function (err, wfd) {
			write(wfd, buf, 0, 30, 0, function (err, written) {
				close(wfd);
			});
		});
	});
});

Stream

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,
	autoClose: true,
	emitClose: true,
});

let arr = [];

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

res.on("data", function (data) {
	console.log("end", data);
	arr.push(data);
});

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

发布订阅

function EventEmitter() {
	this.events = {};
}

EventEmitter.prototype.on = function (eventName, fn) {
	const events = this.events[eventName] || (this.events[eventName] = []);
	events.push(fn);
};

EventEmitter.prototype.emit = function (eventName, ...res) {
	if (!this.events[eventName]) return;
	this.events[eventName].forEach((cb) => {
		cb(...res);
	});
};

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

EventEmitter.prototype.once = function (eventName, cb) {
	const once = (...res) => {
		cb(...res);
		this.off(eventName, once);
	};

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

const eventBus = new EventEmitter();

function handle(msg) {
	console.log("handler1==================", msg);
}
function handle2(msg) {
	console.log("handler2==================", msg);
}
function handle3(msg) {
	console.log("handler3 once==================", msg);
}

eventBus.on("data", handle);
eventBus.on("data", handle2);
eventBus.once("data", handle3);

eventBus.off("data", handle3);
eventBus.emit("data", "hello tianzhen");

// eventBus.off('data', handle)
// eventBus.off('data', handle2)

// eventBus.emit('data', 'hello tianzhen 1')
// eventBus.emit('data', 'hello tianzhen 2')

分析:

  1. 同一事件可能多次发布,所以用数组存储
  2. off其实就是从数组里移除事件名
  3. once执行后需要将自己移除(off)
  4. 移除once事件的时候,由于once事件是在内部定义的,需要单独处理

全局对象

  • __dirname
  • __filename
  • process.cwd()

事件循环

JS引擎本身不实现事件循环,它是由宿主环境实现的。js -> 浏览器,node -> libuv

Node中的其他异步方法

  1. 文件I/O
  2. setImmediate(libuv实现的一个api)同步任务执行完成后立马执行
  3. server.close, socket.on(); 关闭回调
  4. process.nextTick是一个微任务,先于promise执行
  5. poll阶段:轮训等待新的连接和请求等事件,执行IO回调等
@assets/node/node-eventlop.png

简单

setTimeout(() => {
	console.log("timeout");
}, 0);
Promise.resolve().then(() => {
	console.error("promise");
});
process.nextTick(() => {
	console.error("nextTick");
});
// nextTick promise timeout

绕脑

const fs = require("fs");
fs.readFile(__filename, (data) => {
	console.log("readFile");
	setTimeout(() => {
		console.log("timeout");
	}, 0);
	setImmediate(() => {
		console.log("setImmediate");
	});
});
// 输出:readFile、setImmediate、timeout
  1. 第一次循环没有需要执行的异步任务队列
  2. 第二次只有poll阶段有IO回调任务,输出 ‘readFile’
  3. poll阶段检测如果有setImmediate任务队列,则进入check
  4. 执行close callbacks
  5. 第三次进入timer阶段,输出timeout

思考题

async function async1() {
	console.log("async1 started");
	await async2();
	console.log("async end");
}

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");
});

new Promise((resolve) => {
	console.log("promise1");
	resolve();
	console.log("promise2");
}).then(() => {
	console.log("promise.then");
});
console.log("script end.");

浏览器事件循环