前端开发中的模块加载

项目中遇到的小问题

1
2
module.exports = function A(){}
exports.A = function A(){}

两者有什么区别呢

module 是node.js 中Module类的一个实例,module对象中有一个属性exports,Node.js中另外一个环境变量也叫exports,并指向了module.exports
使用module.exports = function A(){},就是把function A直接赋值给了module对象的exports属性
如果是 exports.A = function A(){},则是把function A赋值给了module.exports.A

在别的文件中 require导出的模块时,导出的是module.exports,我们可以通过一个例子来验证一下这个结论

A_module.js

1
2
3
4
5
function A() {
console.log('this is function A');
}
module.exports = A;

A_exports.js

1
2
3
4
5
function A(){
console.log('this is function A');
}
exports.A = A;

我们分别定义了两个js模块,在B.js中将这个两个文件都导入进来,输出下导出的内容
B.js

1
2
3
4
5
var A_module = require('./A_module');
var A_exports = require('./A_exports');
console.log(A_module);
console.log(A_exports);

运行上面的代码,输出结果为:

1
2
[Function: A]
{ A: [Function: A] }

require 方法

require 方法可以接受下面几种参数的传递
  • 原生模块 https,fs,path
  • 文件模块
    • 相对路径的文件模块 ./utils
    • 绝对路径的文件模块 /usr/local/lib/node_modules/express
    • 不带路径的文件模块 lodash
require 方法中,不带路径的文件模块的加载方案

以 express模块举例

1
var express = require('express');
  • 确定 express 的绝对路径可能是下面这些位置,依次搜索每一个目录。

    1
    2
    3
    4
    5
    [ '/Users/yuancheng.yuan/WebstormProjects/nodeModules/node_modules',
    '/Users/yuancheng.yuan/WebstormProjects/node_modules',
    ‘/Users/yuancheng.yuan/node_modules',
    '/Users/node_modules',
    '/node_modules' ]
  • node 先将 express当作文件名,依次尝试去加载下面这些文件,只要有一个就成功返回

    1
    2
    3
    4
    express
    express.js
    express.json
    express.node
  • 如果没有没有找到上述文件,那么会把 express 当作文件夹,于是依次尝试加载下面这些文件

    1
    2
    3
    4
    express/package.json(main字段)
    express/index.js
    express/index.json
    express/index.node
  • 如果在所有目录下都没有找到对应的文件,那么会抛出一个错误
对于不同类型的文件,node会有不同的处理方式
  • xxx.js 通过fs模块同步读取js,并编译执行
  • xxx.json 通过JSON.parse 解析加载
  • xxx.node 通过c/c++ 编写的addon,通过dlopen方法进行加载
详解js文件的加载过程
1
2
3
4
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
};
  • 先将文件转为字符串
  • 剥离 utf-8编码特有的BOM文件头
  • 编译

编译过程简化版
也就是说,模块的加载实质上就是,注入exports、require、module三个全局变量,然后执行模块的源码,然后将模块的 exports 变量的值输出。

1
2
3
4
5
6
7
8
9
Module.prototype._compile = function(content, filename) {
var self = this;
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
};
(function (exports, require, module, __filename, __dirname) {
// 模块源码
});

Node.js模块与前端模块的异同
  • Node.js是commonJS规范的一种实现
  • 浏览器不支持Node.js中的模块机制
  • CMD,AMD 规范
在浏览器中使用commonjs规范的模块
  • 浏览器缺少环境变量module,exports,require,global,只要在浏览器环境中,手动添加这几个变量,方法,就可以让commonJS规范的模块在浏览器中运行
  • Browserify,常用的在打包过程中,对commonJS模块进行包装,使其能够在浏览器中正确运行
通用模块的开发
  • Node.js在模块载入到最终的执行中,进行了包装,使得每个文件中的变量天然的形成在一个闭包之中,不会污染全局变量
  • 而浏览器端则通常是裸露的JavaScript代码片段

在开发通用模块时,可以对运行环境进行判断,如果当前运行环境中包含 exports 变量,那么就把我们的方法挂载到 experts对象上,如果不存在,就把exports对象加载到全局变量中

1
2
3
4
5
if (typeof exports !== "undefined") {
exports.EventProxy = EventProxy;
} else {
this.EventProxy = EventProxy;
}
0%