网易首页 > 网易号 > 正文 申请入驻

从No.js看Node.js原理

0
分享至

  作者:theanarkh来源:编程杂技

  越来越多同学在使用Node.js,大家也不同程度地理解Node.js是什么。比如Node.js是由V8、Libuv、JS组成的,Node.js底层是C\C++,Node.js不是语言是运行时。本文通过实现一个类Node.js的JS运行时No.js,去理解Node.js的本质。No.js是我之前写的一个JS运行时,概念上是这么说,但是它算不上真正的运行时,它只是个demo,但是它让你看到如果你有兴趣,你也可以写个Node.js。

  首先我们看看V8的基本用法。

#include
#include
#include
#include "include/libplatform/libplatform·h"
#include "include/v8.h"
int main(int argc, char* argv[]) {
// Initialize V8.
v8::V8::InitializeICUDefaultLocation(argv[0]);
v8::V8::InitializeExternalStartupData(argv[0]);
std::unique_ptr
platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
// 创建一个Isolate,表示一个隔离的实例
v8::Isolate* isolate = v8::Isolate::New(create_params);
v8::Isolate::Scope isolate_scope(isolate);
// 定义一个HandleScope 管理下面的handle内存的分配和释放
v8::HandleScope handle_scope(isolate);
// 创建一个上下文,js里访问的东西来自context
v8::Local
context = v8::Context::New(isolate);
v8::Context::Scope context_scope(context);
// 定义我们要执行的代码
v8::Local"'Hello' + ', World!'",v8::NewStringType::kNormal).ToLocalChecked();
source = v8::String::NewFromUtf8(isolate,
// 编译脚本
v8::Local
script = v8::Script::Compile(context, source).ToLocalChecked();
// 执行脚本
v8::Local
result = script->Run(context).ToLocalChecked();
// 输出结果
v8::String::Utf8Value utf8(isolate, result);
printf("%s\n", *utf8);
// Dispose the isolate and tear down V8.
isolate->Dispose();
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
delete create_params.array_buffer_allocator;
return 0;

  我们看代码很多,但是大部分的根据V8文档就行,最核心的是context和脚本的定义,我们看到这里的context是V8提供的内容,然后执行的JS脚本也平平无奇。下面我们要做的事情就是拓展这个context,给它注入一下新功能,相应地,在JS里也就能访问V8内置变量之外的变量,我们看看怎么搞。

// 拿到一个全局变量,这个就是我们在js里对应的全局变量
Local global = context->Global();
// 定义一个字符串对象
Localkey = String::NewFromUtf8(isolate, "TCP", NewStringType::kNormal, strlen("TCP")).ToLocalChecked();
Local"dummy", NewStringType::kNormal, strlen("dummy")).ToLocalChecked();
cbdata = String::NewFromUtf8(isolate,
// 定义一个函数
Local func = Function::New(context,
// 执行func时会调用Invoke,cbdata是入参
Invoke,
cbdata).ToLocalChecked();
// 把函数注册到全局变量,这样我们在js里就可以使用key该函数了
Maybe
ignore = global->Set(context, key, func);
// 打开文件
int fd = open(argv[1], O_RDONLY);
struct stat info;
// 取得文件信息
fstat(fd, &info);
// 分配内存保存文件内容
char *ptr = (char *)malloc(info.st_size + 1);
// 读取文件搭配ptr,Mac os的read函数定义第二个参数是void *
read(fd, (void *)ptr, info.st_size);
// 要执行的js代码
Local
source = String::NewFromUtf8(isolate, ptr,
NewStringType::kNormal,
info.st_size).ToLocalChecked();
  1. 上面代码主要分为几个部分。
  2. 1 从context中获取全局变量。
  3. 2 定义一个新功能,并注入到全局变量,这样我们就可以在JS里访问了。
  4. 3 打开一个文件并且读取进来,交给V8编译执行。下面我们看重点,即我们自定义的功能。从注释里我们看到我们给注入了一个TCP的全局变量。他的值是一个函数。当我们在JS里执行TCP这个函数的时候,就会执行我们自定义的C++函数,并传入实参。我们定义的函数是Invoke,我们看看实现。
static void Invoke(const FunctionCallbackInfo
& info) {
Isolate * isolate = info.GetIsolate();
// 新建一个函数模板,模版函数是TCPServer::NewTCPServer
Local
Server = FunctionTemplate::New(isolate, TCPServer::NewTCPServer);
Local"TCPServer", NewStringType::kNormal, strlen("TCPServer")).ToLocalChecked();
tcpServerString = String::NewFromUtf8(isolate,
// 函数名
Server ->SetClassName(tcpServerString);
// 预留一个指针空间,保存一些自定义的上下文
Server->InstanceTemplate()->SetInternalFieldCount(1);
// 设置TCPServer的原型方法
SetProtoMethod(isolate, Server, "socket", TCPServer::TCPServerSocket);
SetProtoMethod(isolate, Server, "bind", TCPServer::TCPServerBind);
SetProtoMethod(isolate, Server, "listen", TCPServer::TCPServerListen);
SetProtoMethod(isolate, Server, "accept", TCPServer::TCPServerAccept);
//SetProtoMethod(isolate, Server, "setsockopt", TCPServer::TCPServerSetsockopt);
info.GetReturnValue().Set(Server->GetFunction(isolate->GetCurrentContext()).ToLocalChecked());

  上面代码看起来很复杂,主要是对V8 API的使用。在V8里,我们自定义的函数格式如下

static void func(const FunctionCallbackInfo
& info) {
info.GetReturnValue().Set(返回值);
入参是FunctionCallbackInfo

  ,函数的返回值通过info.GetReturnValue().Set函数设置。即我们在JS层拿到的内容。上面代码翻译成JS如下。

function Invoke() {
return Server;
// ---
function Server() {
TCPServer.NewTCPServer(this);
Server.prototype.socket = function socket() {
return this[0].socket();
// ---
class TCPServer(){
constructor(target) {
target[0] = this;
this.persistent_handle_ = target;
static NewTCPServer(target) {
new TCPServer(target);
socket() {}
bind() {}

  可以看到,执行Invoke后拿到一个函数TCPServer。然后我们执行new TCPServer,JS代码如下(server.js)

const Server = TCP();
const server = new Server('127.0.0.1', 8989);
server.socket();
server.bind();
server.listen();
while(1) {
server.accept();

  当我们执行new Server的时候。V8首先会创建一个对象obj,然后执行TCPServer.NewTCPServer。并传入obj对象。

static void NewTCPServer(const FunctionCallbackInfo
& info) {
String::Utf8Value ip_address(info.GetIsolate(), info[0]);
int port = info[1].As
()->Value();
// info.This()obj
new TCPServer(info.GetIsolate(),info.This(), *ip_address, port);
// 在this中保存object,析构C++对象的时候需要重置object[0]
TCPServer(Isolate* isolate, Local object, char * ip, int port): _isolate(isolate),persistent_handle_(isolate, object), _ip(ip), _port(port) {
// obj[0]=this
object->SetAlignedPointerInInternalField(0, static_cast
(this));
  1. NewTCPServer同样创建一个对象this,然后通过obj[0]=this关联起来,这是核心逻辑,一会我们会看到有什么用。接下来我们执行一系列网络编程的函数,不过原理是一样的,我们就分析server.socket()。因为server是一个Server实例。所以server.socket() 对应的函数是Server.prototype.socket。这个函数会从this中取出真正对象(TCPServer实例)的socket函数。然后执行它。
// 执行真正对象的socket函数
static void TCPServerSocket(const FunctionCallbackInfo
& info) {
GetTCPServer(info.Holder())->Socket();
// 取出真正的对象,obj[0]
static TCPServer * GetTCPServer(Local object) {
return reinterpret_cast
((*reinterpret_cast
Local*>(&object))->GetAlignedPointerFromInternalField(0));

  从中我们可以看到Server函数是一个透传的作用。它主要用于适配V8的协议。真正的逻辑是在它关联的对象中实现的。其余的实现如下。

int Socket() {
listerFd = socket(AF_INET, SOCK_STREAM, 0);
return listerFd;
int Bind() {
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(_ip);
serv_addr.sin_port = htons(_port);
return bind(listerFd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
int Listen() {
return listen(listerFd, 512);
int Accept() {
int clientFd = accept(listerFd, nullptr, nullptr);
// 返回ok,然后关闭TCP连接
const char * rsp = "connect ok";
write(clientFd, rsp, sizeof(rsp));
close(clientFd);
return 0;
int Setsockopt(int level, int optionName, const void *optionValue, socklen_t option_len) {
return setsockopt(listerFd, level, optionName, optionValue, option_len);
int Close() {
return close(listerFd);

  都是对socket网络编程的封装。最后我们通过No server.js启动服务器,全部代码执行完后,最后阻塞在accept。

while(1) {
server.accept();
这时候我们启动客户端。
const net = require('net');
function handle() {
setTimeout(() => {
const socket = net.connect({host: '127.0.0.1', port: 8989 });
socket.on('connect', () => {
console.log('ok');
socket.destroy();
handle();
}, 1000);
handle();
  • 我们会看到不断输出ok,因为一直在断连重连。至此我们通过拓展V8完成了一个服务器的开发。
  • 后记:本文通过拓展V8实现一个简单的朴素版TCP服务器来了如何拓展V8,而Node.js正是用了这种方式。再封装一下操作系统的文件、网络、进程、线程、IPC等等,我们也可以实现一个Node.js。当然,这是理论上。No.js仓库https://github.com/theanarkh/No.js

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
团队要进行调整?郑钦文社媒取关里巴教练,简介引用尼采名言

团队要进行调整?郑钦文社媒取关里巴教练,简介引用尼采名言

全景体育V
2026-02-24 11:38:06
宗馥莉现身上海高级餐厅,与外籍男子共餐携女同行似一家三口

宗馥莉现身上海高级餐厅,与外籍男子共餐携女同行似一家三口

丁隗解说
2026-02-22 23:48:22
十大元帅和十大大将的待遇

十大元帅和十大大将的待遇

范烽舍长
2026-02-10 15:35:44
是时候重新认识——西门子!

是时候重新认识——西门子!

科学火箭叔
2025-12-09 20:38:29
15天内开战?伊朗迎来强援:2500枚导弹、16架苏35

15天内开战?伊朗迎来强援:2500枚导弹、16架苏35

兵国大事
2026-02-24 00:05:12
征服中年女人,无需套路:两颗真心,一生相守

征服中年女人,无需套路:两颗真心,一生相守

青苹果sht
2025-11-04 06:10:40
曾经的宠儿到现在的弃儿,从1.35亿欧元到350万欧元,身价暴跌97%

曾经的宠儿到现在的弃儿,从1.35亿欧元到350万欧元,身价暴跌97%

替补席懂王
2026-02-24 12:05:23
云南8岁男童虎跳崖遇难!目击者描述事发过程,登山前一家人曝光

云南8岁男童虎跳崖遇难!目击者描述事发过程,登山前一家人曝光

火山詩话
2026-02-23 06:59:31
明明有家却不能回!35万朝鲜族,为何要漂泊在中亚艰难求生?

明明有家却不能回!35万朝鲜族,为何要漂泊在中亚艰难求生?

荷兰豆爱健康
2026-02-23 20:02:08
林俊杰们用行动证明:男人的终极审美,真的很一致

林俊杰们用行动证明:男人的终极审美,真的很一致

橙星文娱
2026-01-03 21:41:06
日本经济长期疲软,日元购买力跌至53年来最低

日本经济长期疲软,日元购买力跌至53年来最低

环球网资讯
2026-02-24 06:44:00
被坑惨了!转会费6500万欧,英超21场仅1球,热刺俯冲降级区

被坑惨了!转会费6500万欧,英超21场仅1球,热刺俯冲降级区

体育世界
2026-02-23 22:08:33
央视披露一起现实版《惊蛰无声》:外籍男子多次请吃烧烤增进感情,一航天科研人员留学时被策反,大量搜集我国航天核心情报,被判刑7年

央视披露一起现实版《惊蛰无声》:外籍男子多次请吃烧烤增进感情,一航天科研人员留学时被策反,大量搜集我国航天核心情报,被判刑7年

扬子晚报
2026-02-23 14:44:43
纽约时报:谷爱凌就像最完美最先进的人工智能,具备神奇运算能力

纽约时报:谷爱凌就像最完美最先进的人工智能,具备神奇运算能力

杨华评论
2026-02-23 15:39:42
儿子跟大S女儿是同学…品冠曝小玥儿近况! 过年砸50万宠岳父母

儿子跟大S女儿是同学…品冠曝小玥儿近况! 过年砸50万宠岳父母

ETtoday星光云
2026-02-23 17:50:19
12部电影累计票房近100亿元,《熊出没》是如何做到的?

12部电影累计票房近100亿元,《熊出没》是如何做到的?

证券时报e公司
2026-02-24 12:05:59
TOP14位身高170以上的女神,有颜有灯有演技

TOP14位身高170以上的女神,有颜有灯有演技

素然追光
2026-01-02 02:45:02
上千家美国企业排队“退税”,尴尬的美国关税战试图挽尊

上千家美国企业排队“退税”,尴尬的美国关税战试图挽尊

第一财经资讯
2026-02-23 21:23:11
战犯黄维被捕后向陈赓吐露:你手下有个旅长,在我这里可以当军长

战犯黄维被捕后向陈赓吐露:你手下有个旅长,在我这里可以当军长

大运河时空
2026-02-23 09:30:03
布伦森:愿意终其一生都效力尼克斯 尼克斯2年后会给他顶薪吗?

布伦森:愿意终其一生都效力尼克斯 尼克斯2年后会给他顶薪吗?

仰卧撑FTUer
2026-02-24 10:42:03
2026-02-24 12:40:49
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 823关注度
往期回顾 全部

科技要闻

AI颠覆发展最新牺牲品!IBM跳水重挫超13%

头条要闻

特朗普:我不知道还能活多久 很多人都想置我于死地

头条要闻

特朗普:我不知道还能活多久 很多人都想置我于死地

体育要闻

苏翊鸣总结米兰征程:我仍是那个热爱单板滑雪的少年

娱乐要闻

杨洋传遇上缅北剧组 开机就离开剧组?

财经要闻

商务部将20家日本实体列入关注名单

汽车要闻

淦家阅定调价值战 吉利高阶智驾加速普及

态度原创

房产
健康
本地
公开课
军事航空

房产要闻

窗前即地标!独占三亚湾C位 自贸港总裁行宫亮相

转头就晕的耳石症,能开车上班吗?

本地新闻

春花齐放2026:《骏马奔腾迎新岁》

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

美军参联会主席警告:对伊朗动武可能带来重大风险

无障碍浏览 进入关怀版