Node.js 模块的写法及 exports 的用法


两种基本写法

person.js – 写法1

exports.hello = function(){
    console.log(‘world’);
}

person.js – 写法2

var person = {
    hello : function(){
        console.log(‘world’);
    }
}

module.exports = person;

user-person.js

var person = require(‘person’);
person.hello();

一种错误的写法

bar.js

var bar = function(){
    console.log(‘it is bar’);
};

exports = bar;

use-bar.js

var bar = require(‘./bar.js’);
bar();  // 这里会报错:TypeError: object is not a function

使用 require 加载模块时返回 module.exports,exports 是 module.exports 的引用(var exports = module.exports),代码“exports=bar” 改变了 exports 的引用,使 exprts 指向了 bar,而不是 module.exports。所以最终返回的 module.exports 并没有被赋值,它只是一个空对象,使用时抛出 TypeError 的错误。

正确的写法如下:

var bar = function(){
    console.log(‘it is bar’);
};

module.exports = bar;

集合

models.js

exports.User = require('./user');
exports.Person = require('./person');

user-models.js

var models = require('./models');
User = models.User;
Product = models.Product;

实际工程中,如果这些模型的模块文件都在同一个目录下面,我们可以使用一条语句就可以全部加载进行并赋值给 module.exports 进行暴露。

models.js

module.exports = require('../lib/models')(__filename);

构造方法

person.js

function Person(name) {
     this.name = name;
}

Person.prototype.hello = function() {
     console.log("Hi, I'm " + this.name);
};

module.exports = Person;

user-person.js

var Person = require('./person');

var person = new Person('Jane');

工场方法

person-factory.js

function Person(name) {
    this.name = name;
}

Person.prototype.hello = function() {
    console.log("Hi, I'm " + this.name);
};

module.exports = function(name){
    return new Person(name);
};

user-person-factory.js

var factory= require('./person-factory');

var person = factory('Jane');
person.hello();

单例的写法

由于 Node 根据模块的绝对路径对模块进行缓存,所以模块直接返回对象时,该对象会被缓存,每次使用这个模块都会返回同一个对象。

function Mongoose() {
      //...
}

// 重新暴露构造函数(额外做法,与单例无关)
Mongoose.prototype.Mongoose = Mongoose;

module.exports = exports = new Mongoose();

偏函数

例1

var isType = function(type){
    return function(obj){
        let str = toString.call(obj);
        return  str == '[object ' + type + ']';
    };
};

exports.isString = isType('String');
exports.isFunction = isType('Function');

例2

var qs = require('./qs');
var parse = require('./parseUrl').parseUrl;


module.exports = function query(options) {
    return function query(req, res, next) {
        if (!req.query) {
            req.query = ~req.url.indexOf('?') ? qs.parse(parse(req).query, options) : {};
        }
        next();
    };
};
var connect = require('connect');
var query = require('connect/lib/middleware/query');
var app = connect();
app.use(query({maxKeys: 100}));

全局对象

should.js

var should = function (obj) {
    return new Assertion(util.isWrapperType(obj) ? obj.valueOf() : obj);
};
//...
exports = module.exports = should;
//...
Object.defineProperty(Object.prototype, 'should', {
    set: function () {
    },
    get: function () {
        return should(this);
    },
    configurable: true
});

use-should.js

require('should');
var user = {
    name: 'Jane'
};
user.name.should.equal('Jane');

Monkey Patch

Monkey Patch 直译出来是猴子补丁,它的意思是在运行时动态修改某个类或者模块,多用于给第三方代码打补丁,一般用于修改第三方代码的bug,或者是添加一些没有的功能。

我们还是来看下 mongoose 这个模块,默认情况它会将 model 的名称转换为小写和复数的形式,例如如果我们将模块的名称命名为 CreditCardAccountEntry,那么它对应的 collection 的名称就是 creditcardaccountentries。但实际上这个名称非常难以阅读,通常我更喜欢使用credit_card_account_entries,而且我希望这能够作为一种通用模式。

这里我只有给 mongoose.model 打补丁,代码如下:

mongoose-model-patch.js

var Mongoose = require('mongoose').Mongoose;
var _ = require('underscore');
var model = Mongoose.prototype.model;
var modelWithUnderScoreCollectionName = function (name, schema, collection, skipInit) {
    collection = collection || _(name).chain().underscore().pluralize().value();
    model.call(this, name, schema, collection, skipInit);
};
Mongoose.prototype.model = modelWithUnderScoreCollectionName;

当这个模块第一次被加载的时候,它会加载mongoose,重新定义Mongoose.prototype.model,这里使用了代理模式,最终这个新的model方法也会使用原来的model方式来实现对应的功能。现在所有Mongoose的实例都有这个新的行为。注意,这里没有给exports赋值,所以使用require加载这个模块是时候返回是空对象,这也说exports所表示的默认值。

这里有一点需要注意的,当你要采用这种模式来改变第三方模块的行为的时候,最好是采用这里所用的方式,采用代理模式,尽可能用第三方模块提供的默认行为了完成你的行为,这可以保证在第三方模块更新后还可以继续使用更新后的功能。

参考

https://gywbd.github.io/posts/2014/11/using-exports-nodejs-interface-design-pattern.html


前一篇:
后一篇:

发表评论