// Set up process.binding() and process._linkedBinding()
{
const bindingObj = Object.create(null);
process.binding = function binding(module) {
module = String(module);
let mod = bindingObj[module];
if (typeof mod !== 'object') {
mod = bindingObj[module] = getBinding(module);
moduleLoadList.push(`Binding ${module}`);
}
return mod;
};
process._linkedBinding = function _linkedBinding(module) {
module = String(module);
let mod = bindingObj[module];
if (typeof mod !== 'object')
mod = bindingObj[module] = getLinkedBinding(module);
return mod;
};
}
// Set up internalBinding() in the closure
let internalBinding;
{
const bindingObj = Object.create(null);
internalBinding = function internalBinding(module) {
let mod = bindingObj[module];
if (typeof mod !== 'object') {
mod = bindingObj[module] = getInternalBinding(module);
moduleLoadList.push(`Internal Binding ${module}`);
}
return mod;
};
}
NativeModule.require = function(id) {
if (id === loaderId) {
return loaderExports;
}
const cached = NativeModule.getCached(id);
if (cached && (cached.loaded || cached.loading)) {
return cached.exports;
}
if (!NativeModule.exists(id)) {
// Model the error off the internal/errors.js model, but
// do not use that module given that it could actually be
// the one causing the error if there's a bug in Node.js
// eslint-disable-next-line no-restricted-syntax
const err = new Error(`No such built-in module: ${id}`);
err.code = 'ERR_UNKNOWN_BUILTIN_MODULE';
err.name = 'Error [ERR_UNKNOWN_BUILTIN_MODULE]';
throw err;
}
moduleLoadList.push(`NativeModule ${id}`);
const nativeModule = new NativeModule(id);
nativeModule.cache();
nativeModule.compile();
return nativeModule.exports;
};
function setupV8() {
// Warm up the map and set iterator preview functions. V8 compiles
// functions lazily (unless --nolazy is set) so we need to do this
// before we turn off --allow_natives_syntax again.
const v8 = NativeModule.require('internal/v8');
// 初始化map遍历器等
v8.previewMapIterator(new Map().entries());
v8.previewSetIterator(new Set().entries());
v8.previewWeakMap(new WeakMap(), 1);
v8.previewWeakSet(new WeakSet(), 1);
// Disable --allow_natives_syntax again unless it was explicitly
// specified on the command line.
// 自此Disable掉--allow_natives_syntax
const re = /^--allow[-_]natives[-_]syntax$/;
if (!process.execArgv.some((s) => re.test(s)))
process.binding('v8').setFlagsFromString('--noallow_natives_syntax');
}
其中v8.previewMapIterator就用到了v8 builtin模块:
// Clone the provided Map Iterator.
function previewMapIterator(it) {
// v8 build-in函数,js中调用时以%开头
// 函数一般在v8内部代码中调用,用户的js代码中调用需使用--allow-natives-syntax标记执行
return %MapIteratorClone(it);
}
2.执行
执行阶段的代码如下:
// There is user code to be run
// 有用户代码要执行
// If this is a worker in cluster mode, start up the communication
// channel. This needs to be done before any user code gets executed
// (including preload modules).
// 如果在集群模式下有worder,需要先初始化
if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_CLUSTER_SETUP_START);
const cluster = NativeModule.require('cluster');
// 实例化worker
// 监听disconnect,newconn等
cluster._setupWorker();
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_CLUSTER_SETUP_END);
// Make sure it's not accidentally inherited by child processes.
delete process.env.NODE_UNIQUE_ID;
}
if (process._eval != null && !process._forceRepl) {
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_START);
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_END);
// User passed '-e' or '--eval' arguments to Node without '-i' or
// '--interactive'
perf.markMilestone(
NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_START);
preloadModules();
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_END);
const {
addBuiltinLibsToObject
} = NativeModule.require('internal/modules/cjs/helpers');
// 为global加上'assert', 'async_hooks', 'buffer'等属性
addBuiltinLibsToObject(global);
evalScript('[eval]');
} else if (process.argv[1] && process.argv[1] !== '-') {
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_START);
// make process.argv[1] into a full path
const path = NativeModule.require('path');
process.argv[1] = path.resolve(process.argv[1]);
const CJSModule = NativeModule.require('internal/modules/cjs/loader');
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_END);
perf.markMilestone(
NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_START);
preloadModules();
perf.markMilestone(
NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_END);
// check if user passed `-c` or `--check` arguments to Node.
if (process._syntax_check_only != null) {
const fs = NativeModule.require('fs');
// read the source
// 查找文件
const filename = CJSModule._resolveFilename(process.argv[1]);
const source = fs.readFileSync(filename, 'utf-8');
// 检测语法,去掉shebang、BOM等
checkScriptSyntax(source, filename);
process.exit(0);
}
CJSModule.runMain();
} else {
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_START);
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_END);
perf.markMilestone(
NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_START);
preloadModules();
perf.markMilestone(
NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_END);
// If -i or --interactive were passed, or stdin is a TTY.
if (process._forceRepl || NativeModule.require('tty').isatty(0)) {
// REPL
const cliRepl = NativeModule.require('internal/repl');
cliRepl.createInternalRepl(process.env, function(err, repl) {
if (err) {
throw err;
}
repl.on('exit', function() {
if (repl._flushing) {
repl.pause();
return repl.once('flushHistory', function() {
process.exit();
});
}
process.exit();
});
});
if (process._eval != null) {
// User passed '-e' or '--eval'
evalScript('[eval]');
}
} else {
// Read all of stdin - execute it.
process.stdin.setEncoding('utf8');
let code = '';
process.stdin.on('data', function(d) {
code += d;
});
process.stdin.on('end', function() {
if (process._syntax_check_only != null) {
checkScriptSyntax(code, '[stdin]');
} else {
process._eval = code;
evalScript('[stdin]');
}
});
}
}
fakeParent.paths = Module._nodeModulePaths(path);
Module._nodeModulePaths = function(from) {
// guarantee that 'from' is absolute.
from = path.resolve(from);
// Return early not only to avoid unnecessary work, but to *avoid* returning
// an array of two items for a root: [ '//node_modules', '/node_modules' ]
if (from === '/')
return ['/node_modules'];
// note: this approach *only* works when the path is guaranteed
// to be absolute. Doing a fully-edge-case-correct path.split
// that works on both Windows and Posix is non-trivial.
const paths = [];
var p = 0;
var last = from.length;
for (var i = from.length - 1; i >= 0; --i) {
const code = from.charCodeAt(i);
if (code === CHAR_FORWARD_SLASH) {
if (p !== nmLen)
paths.push(from.slice(0, last) + '/node_modules');
last = i;
p = 0;
} else if (p !== -1) {
if (nmChars[p] === code) {
++p;
} else {
p = -1;
}
}
}
// Append /node_modules to handle root paths.
paths.push('/node_modules');
return paths;
};
上面文章提到过在src/node.cc中的LoadEnvironment方法会执行
internal/bootstrap/loaders.js
和internal/bootstrap/node.js
,本文就来看看这两个模块做了什么,小伙伴们注意一下,这里会包含威名远扬的vm、模块加载等方面的讲解。GetBootstrapper
首先我们来看下如何获取到
internal/bootstrap/loaders.js
和internal/bootstrap/node.js
文件内容。我们注意到上述两个文件内容的形式如下面所示:
那么是如何获取到其中的函数的呢?
原来在GetBootstrapper中先去执行一下文件,得到了其中的函数。下面去执行的时候,就可以直接执行函数了。
internal/bootstrap/loaders.js
internal/bootstrap/loaders.js
主要用于native模块的loader。函数输入是process、getBinding、 getLinkedBinding、getInternalBinding;输出是NativeModule构造函数和internalBinding方法。下面我们来一步步看下代码:
1.初始化process.binding、internalBinding
process.binding和process._linkedBinding其实调用src/node.cc中的GetBinding和GetLinkedBinding方法。internalBinding调用的是GetInternalBinding方法。
2.引入node_contextify模块
contextify是node中相当重要的一个模块,主要的作用是用来执行js的代码。
ContextifyScript这个js类中,主要挂载RunInContext和RunInThisContext两个方法,后面会做详细介绍。挂载的方法用到了V8中的env->SetProtoMethod来将C++方法挂载到js类的原型上,挂载代码如下所示:
3.定义NativeModule构造函数
NativeModule中主要有id、filename、exports对象等,这也是native模块的数据结构。
NativeModule._source存储的是所有native模块的map,key是模块名称,value是模块的ascII表示。
4.NativeModule.require
顾名思义,NativeModule.require就是用来引用Native模块的。输入是模块id,输出是模块的exports属性。代码如下:
这里做了下面几件事:
编译执行(nativeModule.compile)
上述步骤最为关键的是nativeModule.compile(),下面我们来介绍一下。
这里做了如下几件事:
这里重点创建一下ContextifyScript,因为我们熟知的大名鼎鼎的vm最终也是基于此来实现的。
创建ContextifyScript实例script也就是
new ContextifyScript(source, this.filename)
,实际会调用ContextifyScript类的静态方法new,其实就是实例化了class ContextifyScript
。script.runInThisContext调用的是
class ContextifyScript
的静态方法RunInThisContext,代码如下:上述代码主要检查了参数,调用了静态方法EvalMachine,执行的js代码的关键也在于EvalMachine,我们下面来看一下。
这里主要做了两件事:
watchdog用来监控执行超时,SigintWatchdog用来监听信号。我们下面来看一下watchdog如何实现?
node中的watchdog是利用创建一个新的线程来实现的。这里有一个需要铺垫的点是uv_run的第二个参数代表事件循环模式,UV_RUN_DEFAULT是默认的循环模式,将会不断重复这个循环,直到"循环引用计数器(ref)"减为0。
超时的流程大致如下,在新线程里面,首先执行uv_run把event loop跑起来,当timer到时后,执行
uv_stop
将事件循环终止,接着uv_close执行,关闭了timer handler,这时循环的ref还剩async一个,接着watchdog被析构,给给主线程发送信号,主线程接收到信号后w->isolate()->TerminateExecution(),最后清理了event loop。internal/bootstrap/node.js
internal/bootstrap/node.js
在internal/bootstrap/loader.js
的基础上,做了一系列初始化操作,最终利用CJS模块查找、执行用户的代码。下面将从逻辑流程上展开说明,后面也会重点介绍CJSModule(模块加载也在此)。流程
1.初始化
这里主要做的初始化有如下几点:
这里说明一下setupV8方法,因为它调用了咱们前面提到的v8 builtin模块。代码如下:
其中v8.previewMapIterator就用到了v8 builtin模块:
2.执行
执行阶段的代码如下:
主要做了如下几件事:
CJSModule
1.模块查找
模块查找主要依赖CJSModule._resolveFilename方法,输入为想要引入的模块,输出为模块的真实路径。其代码如下所示:
上述代码首先利用Module._resolveLookupPaths罗列出所有要查找的路径,在利用Module._findPath在其中查找对应模块。
Module._resolveLookupPaths
Module._resolveLookupPaths代码如下所示:
上面注释已经写的比较详细了,其中有几点要注意:
modulePaths根据环境变量HOME和NODE_PATH得到的路径,比如我本地得到的路径是:
parent.paths是在resolveFilename调用该函数时传递下来的参数,表示从现有目录到根目录下的所有node_modules目录,获取的代码如下:
Module._nodeModulePaths获取从起始目录遍历,每一层都加上node_modules。
Module._findPath
Module._findPath方法实在上面列出的查找目录中找到对应的模块,代码如下:
这里将遍历所有目录,在相应目录中再查找对应模块;在每个查找目录中,查找模块也会有一定的优先级:
2.执行
执行的过程时调用CJSModule.runMain(),在其中调用Module._load(),Module._load代码如下:
过程跟NativeModule中的_compile类似:
执行的过程最终调用了CJSModule._compile方法,而CJSModule._compile最终调用的是vm.runInThisContext。
vm.runInThisContext其实就是调用了本文上面描述的ContextifyScript的runInThisContext,简要代码如下:
总结
本文主要从lib/internal中的loader.js和node.js入手,讲述了具体执行js代码的过程,其中还加入了相关的模块查找、vm、contextify等方面的东西。