nodejs细节篇

举个栗子:

// a.js
module.exports = function(){
  dosomething();
}
// b.js 
module.exports = require(a);
// c.js 
console.log(require(b));

b 是项目 c 依赖的一个工具模块,b 依赖 a。希望只在项目 c 中,b 调用 a 时,a 的函数里能注入一些方法 injectSomething()
期望:hack 之后 c 的输出

function(){
  injectSomething();
  dosomething();
}

具体案例比如:在做个人自动化工具时,需要 mock 一些工具的手动输入;在本地构建时,需要修改通用的构建流程

主要方法

  1. 利用模块 cache 篡改模块对象属性
    在模块 a 的类型是 object 的时候,可以在自己的项目 c 中提早 require 模块 a,按照你的需求修改一些属性,这样当模块 b 再去 require 模块 a 时,从缓存中取出的模块 a 已经是被修改过的了
// c.js
const a = require(a);
let oldp = a.p; 
a.p = function(...args){
   injectSomething();
   oldp.apply(this, args);
}
require(b);

缺陷:在某些模块属性是动态加载的情况,不是那么灵敏,而且只能篡改引用对象。但大部分情况下还是能够满足需求的。

  1. 修改require.cache

在遇到模块暴露的是非对象的情况,就需要直接去修改 require 的 cache 对象了

//a.js 暴露的非对象,而是函数
module.exports = function(){
   doSomething();
}
//c.js
const aOld = require(a); 
let aId = require.resolve(aPath);
require.cache[aId] = function(...args){
   injectSomething();
   aOld.apply(this, args);
}
require(b);
(注意点)

require.cache 是一个 key、value 的 map,key 看上去是模块所在的绝对路径,然而是不能用绝对路径直接去用的,需要 require.resolve 来解析路径,解析后才是 cache 中正确的 key 格式
下面对比下区别:

// 模块的绝对路径
/Users/kino/.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/@ali/builder-cake-kpm/node_modules/@ali/cake-webpack-config/index.js

// 用 require.resolve 转义后的结果
/Users/kino/.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/.0.16.23@@ali/cake-webpack-config/index.js

3.修改 require
这种方法是直接去代理 require ,是最稳妥的方法,但是侵入性相对来说比较强。Node.js 文件中的 require 其实是在 Module 的原型方法上,即 Module.prototype.require

const Module = require('module');
const _require = Module.prototype.require;
Module.prototype.require = function(...args){
    let res = _require.apply(this, args);
    if(args[0] === 'a') { // 只修改a模块内容
        injectSomething();
    }
    return res;
}

缺陷:对整个 Node.js 进程的 require 操作都具有侵入性

判断一个文件是否是被直接运行
require.main === module


完全符合CommonJS规范的包目录应该包含如下这些文件

  • package.json:包描述文件。
  • bin:用于存放可执行二进制文件的目录。  lib:用于存放JavaScript代码的目录。
  • doc:用于存放文档的目录。
  • test:用于存放单元测试用例的代码。

可以看到,CommonJS包规范从文档、测试等方面都做过考虑。当一个包完成后向外公布时, 用户看到单元测试和文档的时候,会给他们一种踏实可靠的感觉


```.node文件的加载过程``` 1 第一个步骤是调用uv_dlopen()方法去打开动态链接 库 2 第二个步骤是调用uv_dlsym()方法找到动态链接库中通过NODE_MODULE宏定义的方法地址 >这 两个过程都是通过libuv库进行封装的 >在*nix平台下实际上调用的是dlfcn.h头文件中定义的 dlopen()和dlsym()两个方法;在Windows平台则是通过LoadLibraryExW()和GetProcAddress()这两 个方法实现的,它们分别加载.so和.dll文件(实际为.node文件) >

node中的核心库
1 GYP项目生成工具     一个专有的扩展构建工具node-gyp,这 个工具通过npm install -g node-gyp这个命令即可安装。
2 V8引擎C++库     可以实现JavaScript 与C++的互相调用
3 libuv库     它是Node自身的动力来源之二 。libuv封装的功能包括事件循环文件操作
4 Node内部库     写C++模块时,免不了要做一些面向对象的编程工作,而Node自身提供了 一些C++代码,比如node::ObjectWrap类可以用来包装你的自定义类,它可以帮助实现对 象回收等工作。
5 其他库    其他存在deps目录下的库在编写扩展模块时也许可以帮助你,比如zlib、openssl、 http_parser等。

一个平台下的.node文件在另一个平台下是无法加载执行的,必须重新用各 自平台下的编译器编译为正确的.node文件。

node的内建模块 buffer、crypto、evals、fs、os等模块都是部分通过C/C++编写的。
在Node的所有模块类型中,存在着一种依赖层级关系,即文件模块可能会依 赖核心模块(js编写的),核心模块可能会依赖内建模块(C/C++编写的)。

JavaScript模块的编译

事实上,在编译的过程中,Node对获取的JavaScript文件内容进行了头尾包装。在头部添加 了(function (exports, require, module, __filename, __dirname) {\n,在尾部添加了\n});。 一个正常的JavaScript文件会被包装成如下的样子:

(function (exports, require, module, __filename, __dirname) {

 });

nodejs中的文件加载规则
.js文件       通过fs模块同步读取文件后编译执行。
.node文件    这是用C/C++编写的扩展文件,通过process.dlopen()方法加载最后编译生成的文件。
.json文件    通过fs模块 同步读取文件后,用JSON.parse()解析返回结果。
其余扩展名文件    它们都被当做.js文件载入。

nodejs 中的模块路径查找过程
1 当前文件目录下的node_modules目录。
2 父目录下的node_modules目录。
3 父目录的父目录下的node_modules目录。 沿路径向上逐级递归,直到根目录下的node_modules目录。

技巧篇幅

  1. 如果是.node和.json文件,在传递给require() 的标识符中带上扩展名,会加快一点速度
  2. 同步配合缓存,可以大幅度缓解Node 单线程中阻塞式调用的缺陷。

兼容多种模块规范
能够兼容Node、AMD、CMD以及常见的浏览器 环境中:

;(function (name, definition) {
// 检测上下文环境是否为AMD或CMD
var hasDefine = typeof define === 'function',
// 检查上下文环境是否为Node
hasExports = typeof module !== 'undefined' && module.exports;
if (hasDefine) {
// AMD环境或CMD环境 define(definition);
} else if (hasExports) {
// 定义为普通Node模块 module.exports = definition();
} else {
// 将模块的执行结果挂在window变量中,在浏览器中this指向window对象 this[name] = definition();
}
})('hello', function () {
var hello = function () {};
return hello; });

node处理一对多并行的情况

var after = function (times, callback) { 
    var count = 0, results = {};
    return function (key, value) {
        results[key] = value; 
        count++;
        if (count === times) {
            callback(results); 
        }
    }; 
};

var done = after(times, render);

var emitter = new events.Emitter(); 
var done = after(times, render);

emitter.on("done", done);

emitter.on("done", other);
    fs.readFile(template_path, "utf8", function (err, template) { emitter.emit("done", "template", template);
});

db.query(sql, function (err, data) {
    emitter.emit("done", "data", data); });
    l10n.get(function (err, resources) { emitter.emit("done", "resources", resources);
});

多对一并行

var after = function (times, callback) { 
    var count = 0, results = {};
    return function (key, value) {
        results[key] = value; 
        count++;
        if (count === times) {
            callback(results); 
        }
    }; 
};

var done = after(times, render);

var emitter = new events.Emitter(); 
var done = after(times, render);

emitter.on("done", done);

emitter.on("done", other);
    fs.readFile(template_path, "utf8", function (err, template) { emitter.emit("done", "template", template);
});

db.query(sql, function (err, data) {
    emitter.emit("done", "data", data); });
    l10n.get(function (err, resources) { emitter.emit("done", "resources", resources);
});

其实感觉 webpack 中的 tapable 是一个很不错的任务流工具类 适用于串行

异步 task

//异步流
class AsyncHook{
    
    constructor(){
        this.hooks = [];
    }
    /**
     * 订阅
     * name 当前订阅者 
     * fn   收到消息后执行的函数
    */
    tapAsync(name,fn){
        /**
         * fn 函数接受四个参数 
         * tag |string当前执行者 
         * task|string任务 
         * result |any 前一个执行结果 
         * next | Functon下一个任务 当前的执行结果传给next
        **/
        this.hooks.push({name,fn});
    }
    /**
     *args[0] | any 任务
     *args[1] | Function 最终的回调
     */
    callAsync(...args){
        const task = args[0];
        const done = args[args.length-1];
        let index = 0;
        const next = (result)=>{      
            this.hooks[index]?this.hooks[index]["fn"](this.hooks[index]["name"],task,result,next):done(task,result);
            index++;
        }   
        next();
    }
}
发布了77 篇原创文章 · 获赞 7 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_37653449/article/details/89392081
今日推荐