@JunQiu
2018-09-25T09:58:30.000000Z
字数 5178
阅读 1455
summary_2018/08
language_js
v8andWebkit
当 Node 遇到 require(X) 时,按下面的顺序处理。
(1)如果 X 是内置模块(比如 require('http'))
a. 返回该模块。
b. 不再继续执行。
(2)如果 X 以 "./" 或者 "/" 或者 "../" 开头
a. 根据 X 所在的父模块,确定 X 的绝对路径。
b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
X
X.js
X.json
X.node
c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
X/package.json(main字段)
X/index.js
X/index.json
X/index.node
(3)如果 X 不带路径
a. 根据 X 所在的父模块,确定 X 可能的安装目录。
b. 依次在每个目录中,将 X 当成文件名或目录名加载。
(4) 抛出 "not found"
## Node 定义了一个构造函数 Module,所有的模块都是 Module 的实例。
function Module(id, parent) {
this.id = id; // 位置
this.exports = {}; // ??
this.parent = parent; // 调用者
this.filename = null;
this.loaded = false; // 是否加载完成
this.children = []; // 是否加载其它模块
}
module.exports = Module;
## 每个模块实例都有require方法
Module.prototype.require = function(path) {
return Module._load(path, this);
}
// Module._load的实现
Module._load = function(request, parent, isMain) {
// 计算绝对路径
var filename = Module._resolveFilename(request, parent);
// 第一步:如果有缓存,取出缓存
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
// 第二步:是否为内置模块
if (NativeModule.exists(filename)) {
return NativeModule.require(filename);
}
// 第三步:生成模块实例,存入缓存
var module = new Module(filename, parent);
Module._cache[filename] = module;
// 第四步:加载模块
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}
// 第五步:输出模块的exports属性
return module.exports;
};
## 上面的主要的两个方法
Module._resolveFilename() :确定模块的绝对路径
module.load():加载模块
// Module._resolveFilename()实现
Module._resolveFilename = function(request, parent) {
// 第一步:如果是内置模块,不含路径返回
if (NativeModule.exists(request)) {
return request;
}
// 第二步:确定所有可能的路径
var resolvedModule = Module._resolveLookupPaths(request, parent);
var id = resolvedModule[0];
var paths = resolvedModule[1];
// 第三步:确定哪一个路径为真
var filename = Module._findPath(request, paths);
if (!filename) {
var err = new Error("Cannot find module '" + request + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
}
return filename;
};
Tips:上面有Module.resolveLookupPaths() 和 Module._findPath()两个方法 ,前者用来列出可能的路径,后者用来确认哪一个路径为真。
// Module._resolveLookupPaths()结果:
[ '/home/ruanyf/tmp/node_modules',
'/home/ruanyf/node_modules',
'/home/node_modules',
'/node_modules'
'/home/ruanyf/.node_modules',
'/home/ruanyf/.node_libraries',
'$Prefix/lib/node' ]
Tips:如果给出了具体路径,会直接返回
// Module._findPath()实现
Module._findPath = function(request, paths) {
// 列出所有可能的后缀名:.js,.json, .node
var exts = Object.keys(Module._extensions);
// 如果是绝对路径,就不再搜索
if (request.charAt(0) === '/') {
paths = [''];
}
// 是否有后缀的目录斜杠
var trailingSlash = (request.slice(-1) === '/');
// 第一步:如果当前路径已在缓存中,就直接返回缓存
var cacheKey = JSON.stringify({request: request, paths: paths});
if (Module._pathCache[cacheKey]) {
return Module._pathCache[cacheKey];
}
// 第二步:依次遍历所有路径
for (var i = 0, PL = paths.length; i < PL; i++) {
var basePath = path.resolve(paths[i], request);
var filename;
if (!trailingSlash) {
// 第三步:是否存在该模块文件
filename = tryFile(basePath);
if (!filename && !trailingSlash) {
// 第四步:该模块文件加上后缀名,是否存在
filename = tryExtensions(basePath, exts);
}
}
// 第五步:目录中是否存在 package.json
if (!filename) {
filename = tryPackage(basePath, exts);
}
if (!filename) {
// 第六步:是否存在目录名 + index + 后缀名
filename = tryExtensions(path.resolve(basePath, 'index'), exts);
}
// 第七步:将找到的文件路径存入返回缓存,然后返回
if (filename) {
Module._pathCache[cacheKey] = filename;
return filename;
}
}
// 第八步:没有找到文件,返回false
return false;
};
## module.load()
模块的加载实质上就是,注入exports、require、module三个全局变量,然后执行模块的源码,然后将模块的 exports 变量的值输出。
确定后缀->根据后缀选择加载方式
## 在标记进行中应用程序暂停(stop-the-world marking)
三色标记(白色-灰色-黑色) + 标记工作表(变为灰色)
## 减少标记停顿
增量标记:在增量标记期间,GC将标记工作分解为更小的模块,并允许应用程序在模块之间交替运行,垃圾回收的最大停顿时间可以减少到原来的1/6左右。
增量标记不是免费的。应用程序必须通知垃圾收集器有关更改对象图的所有操作。V8使用Dijkstra风格的写屏障实现通知。object.field = value在JavaScript 中对表单执行每次写操作之后,V8会插入写屏障代码:
// Called after `object.field = value`.
write_barrier(object, field_offset, value) {
// 如果对象是黑色,引用一个白色的对象,则需要加到marking_worklist
if (color(object) == black && color(value) == white) {
set_color(value, grey);
marking_worklist.push(value);
}
}
## 写屏障:由于使用三色标记法,允许用户代码跟垃圾回收同时运行,那么需要保证被黑色对象引用的白色对象不被回收,这就是写屏障的作用。(由于在三色标记算法中,黑色对象已经处理完毕,它不会被重复扫描。那么,这个对象引用的白色对象将没有机会被着色,最终会被误当作垃圾清理。)
## 由于写屏障成本,增量标记可能会降低应用程序的吞吐量。通过使用额外的工作线程,可以改善吞吐量和暂停时间,即并行标记。
并行标记发生在主线程和工作线程上。在整个并行标记阶段暂停应用程序。它是stop-the-world marking的多线程版本。
### 并发标记:主要发生在工作线程上。并发标记正在进行时,应用程序可以继续运行。
### 主线程的每个改变对象图表的操作将会是竞态数据的潜在来源。在某些操作上,主线程需要与工作线程同步。同步代价和复杂度是操作而定。(???解决)