node.js模块简明详解

什么是模块?

我们先简单描述下模块的特点,模块是一个独立的完成某些功能的单位,它应该具有抽象性、封装性(接口),例如u盘就是一个模块,为什么这么所呢,
1.独立性:我们将文件存入u盘,它是可以独立完成文件的存储功能。
2.抽象性:u盘就是将便携式文件存储的这项常用、公用的功能提取出来的产物。
3.封装性:u盘里面有哪些零件,往往从它外面是看不见的,这里这么说会有些歧义,严谨点说就是u盘里面的那些我们不需要关心的零件,u盘都包装了起来,对外只会留给我们一个使用的usb接口(接口在封装性中非常重要)

js模块

js的模块化的发展是一个演进的过程,目的是为了解决命名冲突、文件依赖等问题,一些原始的写法、模块化的工具sea.js、requireJs、再到webpack等工具这些模块化的知识大家可以参考其他资料,这里笔者不赘述,只说关键的、跟要说的node.js模块相关的,先来看代码

    var foo=(function(){
        var bar=123;
        var add=function(v1,v2){
           return v1+v2;
        }
        return {
           add:add
        }
   )()

上面的代码就是js的一个模块,独立完成某些功能和抽象,这里不用说了,就是一个简单add方法,我们重点说下封装,这里使用return的方式对外开放接口, 而在函数里面的其他代码例如bar,就属于被函数封装了。那么为什么要说这一段js代码,其实不管AMD、CMD、commonJs规范或者es6的模块,它们的实现的核心就是函数自调用的这种封装。

node.js模块

首先说node.js模块是采用CommonJS模块规范,那么和上面js模块有什么样的关系,先不用着急,我们先看下下面这个图:


F8CAB508-5665-468D-AB99-C38B9ECE3CBB.png

这是在Chrome控制台上定义一个foo,window对象就会多一个属性foo,我们在页面上加载的js代码都有这样的特点,因为window是全局对象。
对比一下node.js的代码,node.js也有一个全局对象global,会不会和window一样呢,我们看下下面的代码:

foo = 123;
var bar = 456;
console.log('foo--->',global.foo); 
console.log('bar--->',global.bar);

输出结果:


D4DDCDCA-8919-446F-B437-BAC7B1B565C2.png

在一个js里面去定义全局变量foo,foo会变成global的属性,而bar没有,bar变成这个js私有的了,就如同下面这张图:


47BD6253-3382-4A05-97EC-D395B0B2C227.png

在node.js里面,我们把一个js就看成是一个模块,它具有封装性,在这个模块里面的代码(全局变量除外)都是私有的,如果想要被外部调用,那就需要exports与module.exports曝露出去,并且用require去接收。

module.exports与exports

一个node.js模块中也就是一个js中(下面我们都用一个模块代替一个js),module.exports用法如下

//用法1
//直接赋值,可以赋数字、字符串、数组、对象、函数
//注意:一个模块中module.exports只能赋值一次
module.exports=123;
//用法2
//属性赋值,可以多次赋值,赋数字、字符串、数组等都可以
module.exports.foo=123;
module.exports.bar=456;

exports的用法如下

//用法
//exports只能通过属性的方式曝露,曝露数字、字符串、数组等都可以
exports.foo=123;

require

有了代码的曝露,那么也就有代码的引入,require就是用来加载模块的,这里我们先不谈模块化系统,只说加载一个简单模块,module.exports与require如下

//1.js
module.exports=‘囧’
//2.js
var foo=require(‘./1’);
console.log(foo); //输出囧

exports与require如下

//3.js
exports.bar=‘囧’
//4.js
var foo=require(‘./1’);
console.log(foo.bar); //输出囧

通过require得到的就是module.exports,永远是module.exports,那exports呢?module.exports与exports的区别是什么呢?,exports是module.exports的别名,可以理解为 var exports=module.exports,所以exports只能用于属性的赋值,应用场景也是不同的,如果我只想从一个模块中曝露出一个字符串或者一个数字,那要用module.exports,如果用exports则会向上面的4.js那样需要通过属性的形式取,显的很繁琐,如果是用来曝露多个属性,虽然module.exports可以用属性曝露,最好还是用exports,因为写起来简单...对就是这个原因----简单,如下

//5.js
module.exports.foo=123;
module.exports.bar=123;
//6.js
exports.foo=123;
exports.bar=123;

两种写法都可以,所以一次性曝露用module.exports,如果是曝露多个属性、方法、字符串等用exports比较简单。

模拟require

上面我们还遗留了两个问题,1. var bar = 456;这个bar是怎么在一个模块中变成私有的?2.为什么exports是module.exports的别名?
那么下面我们简单模拟下require方法,如下

var fs = require('fs');
//模拟require方法
var myRequire = function (path) {
    var Module = function () {
        this.exports = {}
    }
    var code = fs.readFileSync(path);
    //包装代码
    var packageCode = `(function(exports ,module) {${code}  return module.exports})`;
    console.log(packageCode);
    //获取要执行的方法
    var result = eval(packageCode);
    console.log(result);
    var module=new Module();
    //将module.exports当实参传给exports
    return result(module.exports,module);
}
//调用
var foo = myRequire('./foo.js');
console.log(foo);

首先我们用fs模块将要引入的foo.js的代码读出来,然后包装成下面的代码

(function(exports ,module) {
//foo.js里面的代码
 return module.exports})

现在我们就清楚了为什么一个js里面的代码是具有封装性的了,用的是js模块那提到过的函数函数自调用的方式封装的。

 return result(module.exports,module);

注意我们去调用result这个方法,result指向的就是(function(exports ,module) {//foo.js里面的代码 return module.exports})这个方法,那么这个方法的形参就是exports、module,实参是module.exports、module,也就是说我们在代码里面使用的exports,其实是指向的module.exports。

var Module = function () {
        this.exports = {}
    }
  var module=new Module();

这里我们看到一个module对象,这个对象是用来存储每一个模块的信息,我们可以在模块的代码里面打印下module看一下

console.log(module);
340235D0-BB0D-44F9-833E-019FF690EC82.png

如上图,module里面的exports默认是一个空对象,和我们模拟的写法一致,这也就是我们可以直接是引用module.exports.属性曝露接口的原因,parent、children是用来存被引用和引用的模块的,paths这是npm查找模块的路径,每一个被node加载的模块都有唯一的一个module存储着这个模块的信息,也说明module是动态产生的。笔者在这里只谈基础的模块,至于模块化系统,请参考其他文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容

  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    w_zhuan阅读 3,580评论 2 41
  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宫若石阅读 1,010评论 0 1
  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    Myselfyan阅读 4,043评论 2 58
  • 1 Node.js模块的实现 之前在网上查阅了许多介绍Node.js的文章,可惜对于Node.js的模块机制大都着...
    zlx_2017阅读 1,178评论 0 1
  • 今天办身份证的路上,妈妈跟一个熟人聊到还在上学的弟弟,说了句:“现在才上小学,还不知道什么时候能熬出头”。“熬出头...
    Miranda明霞阅读 1,130评论 0 0