Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node中的模块 #36

Open
SunShinewyf opened this issue Nov 24, 2017 · 0 comments
Open

node中的模块 #36

SunShinewyf opened this issue Nov 24, 2017 · 0 comments

Comments

@SunShinewyf
Copy link
Owner

node.js中的模块机制是基于CommonJs,对于CommonJsmodule部分,可以戳这里进行查看。

模块的加载规范

对于js的模块部分,有好多这方面的文章,所以在这里我就不再赘述了,对于几种模块的加载规范之间的差别,可移步这里

简述 module 定义

node中,每一个文件都被当成一个独立的模块,而且每个模块都有自己的作用域,这就很好地保证了不同模块之间变量的相互污染。因为每个模块被node包装成如下所示:

 (function (exports, require, module, __filename, __dirname) { 
     //the code of singal file
 });

exports属性上的任何方法和属性都可以在外部被调用,模块中的其余变量或属性则不可直接被调用。但是被加载模块中的全局变量可以被外部调用

//a.js

name = 'test';

//b.js
var a = require('./a.js');
console.log(name);

此时可以打印出test,但是将a.js改为如下:

//a.js

var name = 'test';

此时就会报错。

对于node中模块的基础知识,比如文件加载方式这里不展开说,具体可以查看这篇文章

exportsmodulemodule.exports

  • module是当前模块的对象的引用,结构如下:
Module {
 id: '.',
 exports: {},
 parent: null,
 filename: '/Users/uc/Project/index.js',
 loaded: false,
 children: [],
 paths:
  [ ... ] }
  • module.exportsmodule的一个属性对象(如上可知),它是由模块系统创建的,并且最终返回给调用的模块
  • exportsmodule.exports的一个引用,相当于一个快捷键:
exports = module.exports = {}
//类似于
b = a = {};
//类似于
a = {}
b = a;

最终返回给require的是module.exports模块。

两者之间的相互改变有如下几种情况:

  • 当改变了exports的引用时(即exports指向了一个新的对象空间),此时不会影响到module.exports:
module.exports = {
    name: 'a'
}

exports = {
    age: '21'
}

此时module中的exports对象中只有一个name属性,而不会有age属性。因为exports指向了一个新的空间。

  • 当没有改变exports的引用时,并且添加一个module.exports中(并且此时module.exports有显式声明为一个对象实例)没有的属性时,不会改变module.exports:
module.exports = {
    name: 'a'
}

exports.age = 21

此时module中的exports对象中只有name属性,而没有age属性,因为原先的module.exports中没有age属性,无法添加

  • 当没有改变exports的引用时,并且添加一个module.exports中(并且此时module.exports没有显式声明为一个对象实例)没有的属性时,会改变module.exports:
module.exports.name = 'a';

exports.age = 21

此时module中的exports对象中既有name属性又有age属性

  • 当没有改变exports的引用,并且添加一个module.exports中(并且此时module.exports没有显式声明为一个对象实例)有的属性时,会覆盖module.exports原有的属性:
module.exports.name = 'a';

exports.age.name = 'b';

此时module中的exports对象中既有name属性,并且值为b;

总结:其实也就是两个对象之间相互引用的关系,上面的几种情况也是基于这几种情况来说的而已。为了防止这种改变了值但是不生效的情况,可以采用如下策略:

  • 对于要导出的属性,可以简单直接挂到exports对象上
  • 对于要导出类的情况,直接使用module.exports进行导出即可
  • 如果要使用exports导出类,需要使用exports = module.exports = obj进行hack即可

模块之间的相互引用

之前在做项目的时候,遇到一个场景:a模块中引入了b模块,然后b模块又引入了a,然后在b中访问不到a的属性,当时还花了好长时间来排查(捂脸)...
上面这个场景可以被简单复现为如下:

//a.js

const b = require('./b.js');
console.log('在 a 中,b.done = ', b.name);
console.log(b);
exports.name = 'a';

//b.js

const a = require('./a.js');
console.log('在 b 中,a.done = ', a.name);
exports.name = 'b';

此时运行b.js,会发现打印出的b.name=undefined, b={}
因为在brequire a的时候,发现arequireb,为了防止循环引用,a中此时的b只是一个exports未加载完成的副本{},所以没有任何值打印,但是在b中,可以获取到a.name属性。

所以为了避免出现这种情况,应该尽量避免模块之间的相互引用

主模块

官方文档中对于其实有描述,只是有点简单,之后通过实验了一下,才领会了说的啥。

也就是如果当前要执行当前文件,比如a.js,如果此时执行a.js,那么它的require.main === module就为true,但是对于如下场景:

//a.js
exports.a = require.main === module

//b.js
var a = require('./a.js');

console.log(a);

此时打印出来的就是false。

因为每个文件模块都会被包装成一个函数,并且会有一个__filename的参数,而且__filename === require.main.filename,所以可以通过检查 require.main.filename 来获取当前应用程序的入口点。

模块的缓存

模块在第一次被加载之后就被缓存起来了,这意味着以后每一次再调用相同的模块将会返回同样的一个对象。这种处理方式在大多数情况下是很好的。但是如果有一些比较特殊的场景需要删除这个缓存,要怎么做呢

delete require.cache[moduleName]

其中moduleName是你想要删除缓存的模块名,并且是真实存在的。

具体的讨论可以移步这里

总结

以上只是总结一下我对模块这块存在的有误区或者不太了解的地方,涉及的点不太深,只是自己的一点记录,有不对的地方欢迎斧正。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant