我发现每次细看源码都能发现我之前写的一些东西是错误的,去改掉吧,又很不协调,不改吧,看着又脑阔疼……

  所以,这一节再探,是对之前一些说法的纠正,另外再缝缝补补一些新的内容。

  错误在哪呢?在之前的初探中,有这么一块代码:

  // The bootstrapper scripts are lib/internal/bootstrap/loaders.js and
// lib/internal/bootstrap/node.js, each included as a static C string
// defined in node_javascript.h, generated in node_javascript.cc by
// node_js2c.
Local<String> loaders_name =
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
Local<Function> loaders_bootstrapper =
GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);

  当时,我的理解是这样的:

辅助函数则是加载了internal/bootstrap中的两个JS文件,加载的时候参数传入了C++代码生成的特殊对象。

  但是在我调试这块代码的时候,发现根本没有任何readFile的痕迹,才发现事情并没有那么简单,也就是说这个地方压根就没有加载对应的JS文件。

  那么问题来了,既然没有加载这个JS文件,那这个文件有什么意义?何处加载的?

  第一个问题,我猜大概是开发者想让我们直观的了解到加载了什么东西,所以以文件的形式保留在文件夹中方便查看。

  第二个问题,根据注释,可以很快的知道答案,但是当时哪里注意那么多哟。

  简单讲,这个文件的内容以静态的字符串的形式定义在node_javascript.h中,内容则在node_javascript.cc中,并使用node_js2c进行JS代码到C++代码的转换。

  

  问题的答案很简单,探索过程对我来说还是挺心酸的,这里一共有两行代码,首先看第一行。

  FIXED_ONE_BYTE_STRING是一个宏,这里暂不讨论内部实现,根据参数和返回类型可以简单判断这是一个转换函数,可以将const char*类型转换成Local<String>类型,至于Local是什么,可以参考我上一节内容,或者查阅其他的资料。

  对于第二行代码,需要关注的是LoaderBootstrapperSource这个方法,进去之后会发现又是一个调用:

v8::Local<v8::String> LoadersBootstrapperSource(Environment* env) {
return internal_bootstrap_loaders_value.ToStringChecked(env->isolate());
}

  这个internal_bootstrap_loaders_value是一个结构体,形式比较简单(C++代码结构都很简单),源码如下:

static struct : public v8::String::ExternalOneByteStringResource {
// 重写父类函数
// 强制进行const unsigned char[] => const char*的类型转换
const char* data() const override {
return reinterpret_cast<const char*>(raw_internal_bootstrap_loaders_value);
}
// 数组长度
size_t length() const override { return arraysize(raw_internal_bootstrap_loaders_value); }
// 默认delete函数
void Dispose() override { /* Default calls `delete this`. */ }
// const char* => Local<String>的类型转换
v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {
return v8::String::NewExternalOneByte(isolate, this).ToLocalChecked();
}
} internal_bootstrap_loaders_value;

  几个内部的属性作用非常明朗,注释都有写。

  暂时不去深入了解ToStringChecked方法的内部实现,从返回类型来看,最终也是生成一个Local<String>类型的方法,而这个String的源头,就是上面另外一个变量raw_internal_bootstrap_loaders_value。

  这个东西是这么定义的:

static const uint8_t raw_internal_bootstrap_loaders_value[] = { ,,,,,,,,,,,,,,,,,,,,... }

  很长很长的一个数组,uint8_t是unsigned char的别名,也就是说,这是一个超长的char数组。

  根据C++的基础知识,数组的名字本质上是一个指针,指向数组的第一个值,而数组的值又恰好是char类型的,所以说,对该值进行reinterpret_cast<const char*>的转换是不会有问题的。

  那么另外一个问题是,const char*更为熟知的类型是string字符串,这里一个数字数组是怎么变成字符串的?

  干想果然是浪费时间的,我把这个大数组弄到本地自己打印了一下,发现输出的内容竟然是:

  怎么感觉这么熟悉,翻开JS文件,果然……

  简单思考后,原来这里是因为ASCII表转换,把对应的一个个字符转换成了数字保存在字符数组中,真的是恶心啊。

  那么问题就解决了,加载的辅助JS文件内容其实是以字符数组保存在C++中的,获取完整内容后通过对JS到C++的转换,然后执行对应的代码

  既然完成了JS文件内容、文件名的内容获取,下一步就是构建对应的函数体,方法就是GetBootstrapper,源码简化后如下:

// 静态公共方法 专门负责生成初始化辅助函数体
// env => 上下文环境
// source => JS格式的函数字符串
// script_name => 资源名
static Local<Function> GetBootstrapper(Environment* env, Local<String> source,
Local<String> script_name) {
EscapableHandleScope scope(env->isolate());
// ...
// 解析JS字符串并转换成Local<Value>类型
Local<Value> bootstrapper_v = ExecuteString(env, source, script_name);
// 检测返回的数据类型是否是函数并进行强制类型转换
CHECK(bootstrapper_v->IsFunction());
Local<Function> bootstrapper = Local<Function>::Cast(bootstrapper_v); return scope.Escape(bootstrapper);
}

  这里省略了一些无关的错误处理,比较关键的几步可以看注释描述,有几点需要特殊说明一下:

1、关于EscapableHandleScope,正常情况下都是使用的HandleScope来管理作用域的Local,但是如果函数需要返回临时创建的Local,在返回前Local已经被V8的GC进行了处理,这里必须使用EscapableHandleScope类创建一个特殊的scope,并在最后调用Escape方法将指定的Local返回。

2、返回类型的Local<Value>,如果有看过上一节对于V8引擎一些基本概念的讲解,应该会发现Value是所有数据类型的根类,在对类型进行CHECK后再强制转换可以保证类型安全。

  最后看一眼ExecuteString方法:

static Local<Value> ExecuteString(Environment* env,
Local<String> source,
Local<String> filename) {
EscapableHandleScope scope(env->isolate());
// ...
// 编译解析一条龙
ScriptOrigin origin(filename);
MaybeLocal<v8::Script> script =
v8::Script::Compile(env->context(), source, &origin);
MaybeLocal<Value> result = script.ToLocalChecked()->Run(env->context());
// 返回Local<Value>类型的C++代码
return scope.Escape(result.ToLocalChecked());
}

  这里同样省略一些无关代码,可以发现,处理过程非常直白,直接利用Script类对字符串进行编译解析,然后返回执行完后生成的函数体。

  对于"internal/bootstrap/node.js"的加载过程也类似,就不再重复了。

  目前得到了函数体,但是并没有执行,后面再分析这块内容。

深入出不来nodejs源码-内置模块引入再探的更多相关文章

  1. 深入出不来nodejs源码-内置模块引入初探

    重新审视了一下上一篇的内容,配合源码发现有些地方说的不太对,或者不太严谨. 主要是关于内置模块引入的问题,当时我是这样描述的: 需要关注的只要那个RegisterBuiltinModules方法,从名 ...

  2. 深入出不来nodejs源码-V8引擎初探

    原本打算是把node源码看得差不多了再去深入V8的,但是这两者基本上没办法分开讲. 与express是基于node的封装不同,node是基于V8的一个应用,源码内容已经渗透到V8层面,因此这章简述一下 ...

  3. 深入出不来nodejs源码-timer模块(JS篇)

    鸽了好久,最近沉迷游戏,继续写点什么吧,也不知道有没有人看. 其实这个node的源码也不知道该怎么写了,很多模块涉及的东西比较深,JS和C++两头看,中间被工作耽搁回来就一脸懵逼了,所以还是挑一些简单 ...

  4. 深入出不来nodejs源码-编译启动(1)

    整整弄了两天,踩了无数的坑,各种奇怪的error,最后终于编译成功了. 网上的教程基本上都过时了,或者是版本不对,都会报一些奇怪的错误,这里总结一下目前可行的流程. node版本:v10.1.0. 首 ...

  5. 深入出不来nodejs源码-events模块

    这一节内容超级简单,纯JS,就当给自己放个假了,V8引擎和node的C++代码看得有点脑阔疼. 学过DOM的应该都知道一个API,叫addeventlistener,即事件绑定.这个东西贯穿了整个JS ...

  6. 深入出不来nodejs源码-从fs.stat方法来看node架构

    node的源码分析还挺多的,不过像我这样愣头完全平铺源码做解析的貌似还没有,所以开个先例,从一个API来了解node的调用链. 首先上一张整体的图,网上翻到的,自己懒得画: 这里的层次结构十分的清晰, ...

  7. 深入出不来nodejs源码-timer模块(C++篇)

    终于可以填上坑了. 简单回顾一下之前JS篇内容,每一次setTimeout的调用,会在一个对象中添加一个键值对,键为延迟时间,值为一个链表,将所有该时间对应的事件串起来,图如下: 而每一个延迟键值对的 ...

  8. 深入出不来nodejs源码-流程总览

    花了差不多两周时间过了下primer C++5th,完成了<C++从入门到精通>.(手动滑稽) 这两天看了下node源码的一些入口方法,其实还是比较懵逼的,语法倒不是难点,主要是大量的宏造 ...

  9. Nodejs源码解析之module

    modulejs的导入 Require函数详解 module路径解析 module.js的导入 module.js是由node.js在Nodejs程序启动的时候导入的.module.js中使用的req ...

随机推荐

  1. DXP快捷键记录(红色为经常用到的)

    dxp快捷键 1.设计浏览器快捷键:鼠标左击                         选择鼠标位置的文档鼠标双击                         编辑鼠标位置的文档鼠标右击   ...

  2. [leetcode.com]算法题目 - Pow(x, n)

    Implement pow(x, n). class Solution { public: double pow(double x, int n) { // Start typing your C/C ...

  3. [leetcode.com]算法题目 - Triangle

    Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent n ...

  4. Spring中使用StandardServletMultipartResolver进行文件上传

    从Spring3.1开始,Spring提供了两个MultipartResolver的实现用于处理multipart请求,分别是:CommonsMultipartResolver和StandardSer ...

  5. 【ElasticSearch】:Mapping相关

    Mapping 类似数据库中的表结构定义,主要作用如下: 定义Index下的字段名(Field Name). 定义字段类型,例如数值型.字符串型.布尔型等. 定义倒排索引相关配置,比如是否索引.记录p ...

  6. linux防火墙(三)—— iptables语法之匹配条件

    一.iptables规则的匹配条件类型有三类 1.通用匹配:可直接使用,不依赖于其他条件或扩展,包括网络协议.IP地址.网络接口等条件 2.隐含匹配:要求以特定的协议匹配作为前提,包括端口.TCP标记 ...

  7. spring mvc开发过程中的乱码问题

    在保证jsp,xml,数据库,编辑器编码一致的情况下. 1,用户输入中文,后台接收到也是中文,但是保存到数据库时乱码, 解决方法: 链接数据库的url="jdbc:mysql://local ...

  8. SQLAlchemy 快速入门、基础知识

    SQLAlchemy 是Python 编程语言下的一款开源软件.提供了SQL工具包及对象关系映射(ORM)工具. ORM, 全称Object Relational Mapping, 中文叫做对象关系映 ...

  9. 【xsy2194】Philosopher set+线段树合并

    题目大意:给你一个长度为$n$的序列,有$m$次操作,每次操作是以下两种之一: 对某个区间内的数按照升序/降序排序,询问某个区间内数的积在十进制下首位数字是多少. 数据范围:$n,m≤2\times ...

  10. Docker数据管理(五)

    一.什么是数据卷 生成环境中使用docker的过程中,往往需要对数据进行持久化,或者需要多个容器之间进行数据共享,这个就涉及到了容器数据管理 容器中管理数据主要有两种方式: 数据卷:容器内数据之间映射 ...