在使用ReactNative进行开发的时候,我们的工程是模块化进行组织的。在npmjs.com几十万个库中,大部分都是遵循着CommonJS规则的。在ES6中引入了class的概念,从此JavaScript也可以更加方便地进行OOP编程。但是不变的是,即使在使用OOP编程,其依赖组织方式仍然是模块化的。因此,我们十分有必要了解JavaScript中模块的基本原理,以便在之后的开发过程中能少犯错误,更好的理解整个工程的结构。
本文以更容易理解的意识流的方式简要的介绍了一下require.js、module、exports以及ES6中的Module中的一些差异以及注意事项。如有不对之处欢迎批评指正,另外欢迎讨论技术问题。
本文结构:
- 从require语句说起
- module是什么鬼?
- 来点正常点的写法:exports的用法
- module.exports与exports傻傻分不清楚
- 简单概括模块加载机制
- ES6中的Module以及差异
- 虾米ReactNative模块规范
从 require语句说起
require语句在日常开发中十分的常见,它经常出现在.js
文件的头部,也可以出现在某段语句中。举个:
//文件 module.js
console.log(module);
//文件 index.js
require('./module.js');
在命令行下执行 node index.js
,结果输出如下:
Module {
id: '/Users/xiadongxiang/JS/module/module.js',
exports: {},
parent:
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/xiadongxiang/JS/module/index.js',
loaded: false,
children: [ [Circular] ],
paths:
[ '/Users/xiadongxiang/JS/module/node_modules',
'/Users/xiadongxiang/JS/node_modules',
'/Users/xiadongxiang/node_modules',
'/Users/node_modules',
'/node_modules' ] },
filename: '/Users/xiadongxiang/JS/module/module.js',
loaded: false,
children: [],
paths:
[ '/Users/xiadongxiang/JS/module/node_modules',
'/Users/xiadongxiang/JS/node_modules',
'/Users/xiadongxiang/node_modules',
'/Users/node_modules',
'/node_modules' ] }
从上面的代码以及输出,我们可以归纳出:
- 调用require的时候,require所指向的代码会被解释执行一遍。
- 从输出上看,这是对于一个模块的描述对象,其中id使用了文件的绝对路径,我们可以推断出在RN、Node开发中:文件即模块。
- 此外,关于require之后的文件路径的写法非常简单,看一下上面的输出中的
paths
字段,聪明的你肯定猜到了一些东西。具体也可以参考一下这个链接。
module是什么鬼?
在module.js
中调用的module
,是从哪里来的? 莫慌,我们一步一步探究。
首先,在javascript运行环境中有一个叫做global
的变量(如果在浏览器中叫做window
),有兴趣的同学可以把global
打印在控制台上看看,global打印出来是一个对象。
我们会发现我们常用的console
是global
对象的其中一个属性。而我们平时在使用的时候不需要写global.console.log('hello')
,而只需要写console.log('hello')
.对于大多数场景下,我们只需要知道global有这么一个提供命名空间的作用就行了。
熟悉C++的同学可能比较容易联想到using namespace std;
.
废话了那么多,既然global为我们提供了命名空间,那么上一节中打印出来的东西是否global中的呢?试一试便知:
我们稍微修改一下上一小节的代码:
//文件 module.js
console.log(global.module);
//文件 index.js
require('./module.js');
运行结果:
undefined
这个结果说明了,module不是global命名空间下的东西,那么它到底来自哪里???
来点正常点的写法:exports的用法
上面那个问题,我们先放一下,答案将在模块加载机制中揭晓。这小节先深入探究一下exports的用法。
以下是正常的代码:
//index.js
var a = require('./module.js');
console.log('in parent:',a);
a.a = 2;
console.log(a.a,a.getA());
//module.js
var a = 1;
function getA() {
return a;
}
exports.a = a;
exports.getA = getA;
console.log('in module: ',module.exports);
运行node index.js
得到结果:
in module: { a: 1, getA: [Function: getA] }
in parent: { a: 1, getA: [Function: getA] }
2 1
得到的结论是:
- 通过
require()
调用返回的结果是module.exports
,而这个exports
则是一个对象,在模块内部,大多数情况下可以等同于module.exports
. - 通过输出值
2 1
,推断出:exports.a只是对模块内部的var a;
做了一次浅拷贝。如果a是一个Object的场景,我们可以用指针去理解,同样也是浅拷贝。
module.exports与exports傻傻分不清楚
有哪些情况下exports不等同于module.exports呢?
直接上代码:
//index.js
var a = require('./module.js');
console.log('in parent:',a);
//module.js
var a = 1;
function getA() {
return a;
}
exports.a = a;
exports.getA = getA;
module.exports = {
newA:1,
newB:2
}
console.log('in module module.exports: ',module.exports);
console.log('in module exports: ',exports);
运行node index.js
,结果如下:
in module module.exports: { newA: 1, newB: 2 }
in module exports: { a: 1, getA: [Function: getA] }
in parent: { newA: 1, newB: 2 }
其实这个现象很好理解,我们可以想象在你的代码运行之前,系统做了这些事情:**exports只是module.exports的浅拷贝**.
var module = {
exports:{},
...
};
var exports = module.exports;
//接下来运行你的代码
当然,你若是想不开,也可以给module = {};
重新赋值试试... :)
一般情况下两者的使用场景,没有绝对的对与错:
- module.exports 一般用于只输出一个东西。多为class,function,Object;
- exports. 一般用于输出多种东西;
继续举个方便理解:**module.exports***
//index.js
var A = require('./module.js');
var a = new A();
//module.js
class a {
}
module.exports = a;
exports.*可以利用到对象解构赋值:
//index.js
var {A,B} = require('./module.js');
console.log(A,B()); // 1 1
//module.js
var a = 1;
exports.A = a;
exports.B = function(){
return a;
}
简单概括模块加载机制
我们知道,javaScript
是脚本语言,执行方式是解释执行。注定了JSContext
是非常灵活的,我们不能用强类型语言的标准去理解。
模块加载原理还是比较复杂的,用最简单的方式去解释,当一个文件被require
的时候,其实我们可以理解成:
(function (exports, require, module, __filename, __dirname) {
// 模块源码 --begin
// 模块源码 --end
return module.exports;
});
具体一点:
- 计算绝对路径
- 如果有缓存,取出缓存
- 是否为内置模块
- 生成模块实例,存入缓存
- 加载模块
- 输出模块的exports属性
ES6中的Module以及差异
在ES6中引入了 import from
、export
以及export default
等更加先进的语法。
但是万变不离其宗,语法和更详细的介绍可以参考阮一峰老师的ECMAScript 6 入门.
这里只提2点:
- ES6是动态加载(类似Lazy Load)的,在某些程度上启动起来更快.
- ES6中export出去的是引用!!是引用!!是引用!!
举个:
#include <iostream>
using namespace std;
int main() {
int a = 1;
int b = a;
int &c = a;
b = 2;
c = 3;
cout << a <<endl;
int *_a = new int(1);
int *_b = _a;
int *&_c = _a;
cout<< *_a <<','<<*_b<<','<<*_c<<endl;
delete _a;
_b = new int(2);
_c = new int(3);
cout<< *_a <<','<<*_b<<','<<*_c<<endl;
delete _a;
delete _b;
return 0;
}
输出:
3
1,1,1
3,2,3
其中b的做法是commonJS,c的做法是es6.