首先我们要明白一个前提,CommonJS模块规范和ES6模块规范完全是两种不同的概念。
CommonJS模块规范
CommonJS模块规范,Node是由一个个模块组成。
根据这个规范,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。并且Node为每个模块提供一个exports变量,指向module.exports。
- module.exports 初始值为一个空对象 {}
- exports 是指向的 module.exports 的引用,exports该对象将函数内部的局部变量或函数暴露到外面;
- require() 返回的是 module.exports 而不是 exports,最终所共享的结果是以module.exports所指向的对象为准。
- 也就是: exports = module.exports = {}, exports和module.exports都指向一个引用地址{},如果exports.name = 'xxx',那module.exports = {name:'yyy'},引用对象改变,两者又是同时指向一个对象,所以最终共享的结果是module.exports = {name:'yyy'};若module.exports.name = 'yyy',谁在后就共享谁;若共享的属性不一样都会共享出去。
- exports只能通过.语法向外暴露变量(exports.xxx = "xxx");module.exports即可以通过点语法,也可以直接赋值一个对象(module.exports.xxx = "yyy"或module.exports = {xxx :"yyy")
先说说它们之间的区别:
- exports只能使用.语法来向外暴露内部变量:如expotrs.xxx = xxx;
- module.exports既可以通过.语法,也可以直接赋值一个对象。
我们要明白一点,exports和module.exports其实是一个东西,不信我们来输出一下
console.log(module.exports === exports); //输出结果为:true
输出结果是true其实就说明它们就是一个东西,其实exports = module.exports,因为他们是引用类型的一个变量名,所以当exports再指向一个引用类型的时候,那么他们就不再全等。
exports = [0, 1]; console.log(exports === module.exports); //输出结果为:false
输出结果是true其实就说明它们就是一个东西,其实exports = module.exports,因为他们是引用类型的一个变量名,所以当exports再指向一个引用类型的时候,那么他们就不再全等。
exports = [0, 1]; console.log(exports === module.exports); //输出结果为:false
当然,如果直接通过expotrs.xxx的形式赋值,那么他们依然会指向同一个地址:
exports.array = [0, 1]; console.log(exports === module.exports); //输出结果为:true
这个时候要明白module.exports和exports的区别,就要清楚什么是值类型,什么是引用类型。我对值类型和引用类型的理解就是,看它是存储在栈上,还是存储在堆上,值类型就是存储在栈上,引用类型是存储在堆上,但是有个很特殊的情况是,引用类型的名字,是存储在栈上,然后这个名字指向了堆上的一个地址,从而可以直接使用变量名,调用堆上的数据。
这样可能有点难以理解,我们用代码来简单的认识一下值类型:
let a = 1; let b = a; a = 2; console.log("a的值是:" + a); console.log("b的值是:" + b);
我们将1赋值为a,然后将a赋值给b,然后我们将2赋值为a那么这个时候a和b分别是多少呢?我们将它运行一下看结果:
那么为什么a的值是2,b的值为1呢?是因为当将a赋值给b的时候,相当于是将a的值拷贝给了b也就相当于是重新生成了一个b,那么这个b与a就没有什么关系了,如下图所示:
可以看出,这个时候再去改变变量a的值,那么b的值肯定不会发生变化。
那么引用类型呢:
let a = [1, 2]; let b = a; a[0] = 0; console.log("a的值是:" + a); console.log("b的值是:" + b);
那么问题来了,这个时候的b会输出什么结果,是[1,2]还是[0,2]那么我们来进行输出一下
这个时候为什么输出的结果是0,2呢?这里就涉及到引用类型了,如下图所示:
从图上面看的出来,栈中只是存储了一个变量名字,而数组是存储在堆中。而当将a赋值给b的时候,并不是从堆中拷贝一个数组再让b指向这个数组,而是直接将b指向和a指向的同一个数组。简单来说,可以把变量a看做是银行账户的存折,变量b是银行账户的卡,都是同一个账户,你从存折里面取钱或者存钱,那么卡中的钱也会跟着变多或者变少。
那么我们回到主题,那么我们再来说为什么module.exports可以赋值一个对象,而exports却不可以。要明白这点,就要从nodejs的模块化说起,当nodejs执行模块中的代码时,它会将模块中的代码,用下面的函数包裹起来:
function (exports, require, module, __filename, __dirname) {}
这里面的其他参数就在这篇文章中不仔细讲解了,不过可以发现,里面有个熟悉的参数module。这里就要说到exports的本质了,正如上面所说exports = module.exports,也就是说他们指向了堆空间的同一个东西,如果对exports进行赋值,那么exports的指向就不一样了,在另外的文件里面就无法再找到通过exports这个变量传递的东西,而module.exports是在执行模块代码中就将module传入到了函数中,所以即使module.exports的值改变也能够在其他文件中进行调用。
require:用于引入外部模块、 JSON、或本地文件。 可以从 node_modules 引入模块。 可以使用相对路径(例如 ./、 ./foo、 ./bar/baz、 ../foo)引入本地模块或 JSON 文件,路径会根据 __dirname 定义的目录名或当前工作目录进行处理。
__filename 和 __dirname这两个变量是预定义在Node.js中的:
__filename主要用以获取当前模块文件被解析过后的绝对路径
__dirname主要用以获取当前模块文件解析过后所在的文件夹(目录)的绝对路径
__filename变量
在Node.js中,我们可以在模块内(或者说一个js文件内),使用 __filename变量来获取模块文件的带有完整绝对路径的文件名。
例如,我在桌面新建了一个test.js文件(Mac系统),里面有以下一行代码,注释里的内容就是输出的结果。
console.log(__filename); // /Users/Meskjei/Desktop/test.js
__dirname变量等同于path.dirname(__filename)
使用方法同上,不同的地方在于,__dirname变量获取的是模块文件的完整绝对路径(可理解为__filename变量去掉文件名只保留了路径)。我把上面的代码修改为:
console.log(__dirname); // /Users/Meskjei/Desktop console.log(path.dirname(__filename));
补充:相对路径和绝对路径
再写到这里的时候我产生了一个疑问,如果将module.exports的指向改变,那么通过exports.xxx传递的值在其他文件中还能进行调用嘛,于是我尝试了下面的代码:
//test.js exports.add = 100; module.exports = 1; //test1.js文件 let test = require("./test"); let p = test.add; let b = test; console.log("p的值是:" + p); console.log("b的值是:" + b); /* 输出结果是: p的值是:undefined b的值是:1 */
可以看出,改变了module.exports的指向后,exports.xxx的值在其他文件中也无法调用。
ES6模块规范
不同于CommonJS,ES6使用 export 和 import 来导出、导入模块。
// profile.js var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export {firstName, lastName, year};
导入:
import {firstName, lastName, year} from 'profile.js'
需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
// 写法一 export var m = 1; // 写法二 var m = 1; export {m}; // 写法三 var n = 1; export {n as m};
export default 命令
使用export default命令,为模块指定默认输出。
// export-default.js export default function () { console.log('foo'); }
摘自: