@JunQiu
2018-09-25T01:58:30.000000Z
字数 5178
阅读 1786
summary_2018/08 language_js v8andWebkit
当 Node 遇到 require(X) 时,按下面的顺序处理。(1)如果 X 是内置模块(比如 require('http'))a. 返回该模块。b. 不再继续执行。(2)如果 X 以 "./" 或者 "/" 或者 "../" 开头a. 根据 X 所在的父模块,确定 X 的绝对路径。b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。XX.jsX.jsonX.nodec. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。X/package.json(main字段)X/index.jsX/index.jsonX/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, .nodevar 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.jsonif (!filename) {filename = tryPackage(basePath, exts);}if (!filename) {// 第六步:是否存在目录名 + index + 后缀名filename = tryExtensions(path.resolve(basePath, 'index'), exts);}// 第七步:将找到的文件路径存入返回缓存,然后返回if (filename) {Module._pathCache[cacheKey] = filename;return filename;}}// 第八步:没有找到文件,返回falsereturn 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_worklistif (color(object) == black && color(value) == white) {set_color(value, grey);marking_worklist.push(value);}}
## 写屏障:由于使用三色标记法,允许用户代码跟垃圾回收同时运行,那么需要保证被黑色对象引用的白色对象不被回收,这就是写屏障的作用。(由于在三色标记算法中,黑色对象已经处理完毕,它不会被重复扫描。那么,这个对象引用的白色对象将没有机会被着色,最终会被误当作垃圾清理。)## 由于写屏障成本,增量标记可能会降低应用程序的吞吐量。通过使用额外的工作线程,可以改善吞吐量和暂停时间,即并行标记。并行标记发生在主线程和工作线程上。在整个并行标记阶段暂停应用程序。它是stop-the-world marking的多线程版本。

### 并发标记:主要发生在工作线程上。并发标记正在进行时,应用程序可以继续运行。### 主线程的每个改变对象图表的操作将会是竞态数据的潜在来源。在某些操作上,主线程需要与工作线程同步。同步代价和复杂度是操作而定。(???解决)

