exprots && module.exports

在一个模块文件中定义的本地(即非函数内部定义的)变量、函数或者对象都只在该模块内部有效,当你需要从模块外部引用这些变量、函数或者对象时,需要在该模块内部使用exports或者module.exports对象。

开发者可以在“全局”环境下任意使用var申明变量(不用写到闭包里了),通过exports暴露接口给调用者。

是否纠结于什么时候该用exports什么时候该用module.exports呢?先简要陈述一下两者的区别,分别给出两个例子后,在具体说一下使用的场景。

  • 对于要导出的属性,可以简单直接挂到exports对象上

  • 对于类,为了直接使导出的内容作为类的构造器可以让调用者使用new操作符创建实例对象,应该把构造函数挂到module.exports对象上,不要和导出属性值混在一起

exports

测试一下

创建一个文件作为我们自定义的格式化工具类,起名叫做format.js。在函数内我们定一个格式化金钱的函数formatMoney并使用exports对外导出,再定义一个函数,不导出

function formatMoney(s, n) {
  if (isNaN(s)) {
    return '--';
  }
  var negative = false;
  if (s < 0) {
    negative = true;
    s = Math.abs(s);
  }
  try {
    n = n >= 0 && n <= 20 ? n : 2;
    s = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";
    var l = s.split(".")[0].split("").reverse(), r = s.split(".")[1];
    t = "";
    for (i = 0; i < l.length; i++) {
      t += l[i] + ((i + 1) % 3 === 0 && (i + 1) !== l.length ? "," : "");
    }
    r = r ? '.' + r : '';
    var result = t.split("").reverse().join("") + r;
    if (negative) {
      return '-' + result;
    } else {
      return result;
    }

  }
  catch (e) {
    return '';
  }
}

function funcOnlyMe() {
    console.log('This function can not be used by other file')
}


exports.formatMoney = formatMoney;

在同层的test.js文件中引用模块文件,调用模块暴漏给外部的方法和私有方法

var format = require('./format');

var moneyOutput1 = format.formatMoney('1982400');
console.log(moneyOutput1);
//1,982,400.00

format.funcOnlyMe();

// format.funcOnlyMe()
//        ^

// TypeError: format.funcOnlyMe is not a function
//     at Object.<anonymous> (/Users/beifeng/Desktop/test_node/test.js:6:8)
//     at Module._compile (module.js:398:26)
//     at Object.Module._extensions..js (module.js:405:10)
//     at Module.load (module.js:344:32)
//     at Function.Module._load (module.js:301:12)
//     at Function.Module.runMain (module.js:430:10)
//     at startup (node.js:141:18)
//     at node.js:1003:3

module.exports

需要将模块定义为一个类的时候,需要使用module.exports的书写方法。

将模块封装成类的演示1

// module_test.js
var _name;
var name = '';

// 模块对象的构造函数
var foo = function(name) {
    _name = name;
}

//获取私有变量 _name的变量值
foo.prototype.GetName = function() {
    return _name;
}

//设置私有变量 _name的变量值
foo.prototype.SetName = function(name) {
    _name = name;
}

foo.prototype.name = name;

module.exports = foo;

//main.js
var foo = require('./module_test');

var myFoo = new foo('feng');
console.log(myFoo.GetName());//feng

myFoo.SetName('liang');
console.log(myFoo.GetName());//liang

封装一个自定义的格式化价格的工具

module.exports = {
  formatMoney: function (s, n) {
    if (isNaN(s)) {
      return '--';
    }
    var negative = false;
    if (s < 0) {
      negative = true;
      s = Math.abs(s);
    }
    try {
      n = n >= 0 && n <= 20 ? n : 2;
      s = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";
      var l = s.split(".")[0].split("").reverse(), r = s.split(".")[1];
      t = "";
      for (i = 0; i < l.length; i++) {
        t += l[i] + ((i + 1) % 3 === 0 && (i + 1) !== l.length ? "," : "");
      }
      r = r ? '.' + r : '';
      var result = t.split("").reverse().join("") + r;
      if (negative) {
        return '-' + result;
      } else {
        return result;
      }

    }
    catch (e) {
      return '';
    }
  },
  formatWan: function (s, n) {
    s = s / 10000;
    n = n || 0;
    return this.formatMoney(s, n);
  },
  formatYi: function (s, n) {
    s = s / 100000000;
    n = n || 0;
    return this.formatMoney(s, n);
  },
  moneyUnit: function (money) {
    if (parseFloat(money) < 1) {
      return money * 100 + '分'
    } else if (parseInt(money) < 10000) {
      return money + '元'
    } else if (parseInt(money) < 100000000) {
      return this.formatWan(money, 2) + '万元'
    } else {
      return this.formatYi(money, 2) + '亿元'
    }
  }
}

exports 和 module.exports区别

Node.js在模块编译的过程中会对模块进行包装,最终会返回类似下面的代码:

(function (exports, require, module, __filename, __dirname) {
    // module code...
});

其中,module就是这个模块本身,require是对Node.js实现查找模块的模块Module._load实例的引用,__filename和__dirname是Node.js在查找该模块后找到的模块名称和模块绝对路径,这就是官方API里头这两个全局变量的来历。

关于module.exports与exorts的区别,了解了下面几点之后应该就完全明白:

模块内部大概是这样:

exports = module.exports = {};
  • exports是module.exports的一个引用。

  • require引用模块后,返回给调用者的是module.exports而不是exports

  • exports.xxx相当于在导出对象上挂属性,该属性对调用模块直接可见

  • exports =相当于给exports对象重新赋值,调用模块不能访问exports对象及其属性

  • 如果此模块是一个类,就应该直接赋值module.exports,这样调用者就是一个类构造器,可以直接new实例

也就是说,首先以下几种导出方式是一致的

exports.str = 'a';  
exports.func = function () {}; 

module.exports = {
    str : 'a',
    func : function(){}
}


exports = module.exports = {
    str : 'a',
    func : function(){}
}

引用这个模块的文件,看到的都是相同的内容

var foo = require('./module_test');

console.log(foo)
//{ str: 'a', func: [Function] }

但是如果修改重写exports属性的话,情况就大不一样了。在下面文件中,我们先给exports对象设置两个属性,然后重写exports属性,最后再给exports对象设置两个新属性

exports.str1 = 'before overwrite exports'
exports.func1 = function () {
    console.log(this.str1);//此处的this代表当前导出的exports对象
}; 
exports = {
    str2:'inner exports',
    func2:function(){}
}
exports.str3 = 'after overwrite';  
exports.func3 = function () {}; 

引用这个模块的文件

var foo = require('./module_test');

console.log(foo)
//{ str1: 'before overwrite exports', func1: [Function] }

发现一个有趣的现象:重写exports对象的一刻起,exports对象就失去了继续对外导出属性的能力,外部文件只能访问重写之前的exports对象属性。

什么时候使用exprots 什么时候使用module.exports

有一点需要重申:exports只是module.exports的辅助方法。你的模块最终返回module.exports给调用者,而不是exportsexports所做的事情是收集属性,如果module.exports当前没有任何属性的话,exports会把这些属性赋予module.exports。如果module.exports已经存在一些属性的话,那么exports中所用的东西都会被忽略。

module.exports = 'hello world';
exports.name = function() {
    console.log('sorry , i am fail');
};

引用这个模块的文件

var foo = require('./module_test.js');
foo.name(); // TypeError: Object hello world has no method 'name'

foo模块完全忽略了exports.name,然后返回了一个字符串’hello world ’。通过上面的例子,你可能认识到你的模块不一定非得是模块实例。你的模块可以是任何合法的JavaScript对象 - boolean,number,date,JSON, string,function,array和其他。你的模块可以是任何你赋予module.exports的值。如果你没有明确的给module.exports设置任何值,那么exports中的属性会被赋给module.exports中,然后并返回一个包含这些属性的对象。

如果你的模块是一个类

module.exports = function(name, age) {
    this.name = name;
    this.age = age;
    this.about = function() {
        console.log(this.name +' is '+ this.age +' years old');
    };
};

这样调用它

var foo = require('./module_test.js');
var people = new foo('feng', 30);
people.about(); // feng is 26 years old

在下面的情况下,你的模块是一个数组:

module.exports = ['a', 'b', 'c', 'd', 'e'];

然后这样调用

var foo = require('./module_test.js');
console.log('which one: ' + rocker[2]); //c

所以:如果你想要你的模块成为一个特别的对象类型,那么使用module.exports;如果你希望你的模块成为一个传统的模块实例,使用exports,因为这货基本上只能返回一个包含若干属性的对象。

话虽如此,exports还是推荐的对象,除非你想把你模块的对象类型从传统的模块实例修改为其他的类型的返回值。

希望对您有用~

推荐阅读更多精彩内容