node.js模块简明详解

作者: 炙热地瓜 | 来源:发表于2017-09-19 15:44 被阅读89次

    什么是模块?

    我们先简单描述下模块的特点,模块是一个独立的完成某些功能的单位,它应该具有抽象性、封装性(接口),例如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是动态产生的。笔者在这里只谈基础的模块,至于模块化系统,请参考其他文章。

    相关文章

      网友评论

        本文标题:node.js模块简明详解

        本文链接:https://www.haomeiwen.com/subject/warfsxtx.html