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还是推荐的对象,除非你想把你模块的对象类型从传统的模块实例修改为其他的类型的返回值。

希望对您有用~

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

推荐阅读更多精彩内容