Sunday, March 6, 2016

Difference between exports and module.exports in Node.js

When you have large javascript file, you want to break that file into independent modules so that you can reuse each module in different files.  Node.js follows CommonJS module system in which modules are imported using require method available in global scope. For example, lets say you have two files, person.js and main.js. To import person.js functionality into main.js, you would do this

var Person = require('person');

Now you can use methods defined in person.js in main.js using Person variable. To be able to get the Person variable from person.js file, we should have return statement inside the person.js file so that require method assigns return value to the Person variable.  person.js would look like this


'use strict';
module.exports = function(name, age) {
     return {
   get_name: function() {return name},
   get_age: function() {return age}
     }
}

And, main.js would look like this

'use strict';
var Person = require('./person');
var person = new Person('Bibek', 25);
console.log(person.get_name());

Running main.js would give name of the person.

So far I showed you way of importing file into another file using module.exports. But what if I replace module.exports with exports in above code? ... this wont work. This is really confusing for beginner.

To illustrate why replacing module.exports with exports doesn't work, let me revise your basic Javascript knowledge.

Consider following code snippet

'use strict';

// initialize two empty objects
var object2 = {}
var object1 = object2;

// Mutate object1
object1.name = "object name";

// print name perperty for both objects
console.log(object1.name); //outputs 'object name'
console.log(object2.name); //outputs 'object name'

// similary mutate object2
object2.name = "another name";

// again print name for both objects
console.log(object1.name); //outputs 'another name'
console.log(object2.name); //outputs 'another name'

// assign object 1 values
object1 = {name : "assigned name"}

// print objects
console.log(object1.name); //outputs 'assigned name'
console.log(object2.name); //outputs 'another name' ?????

// similary assgn object 2 value
object2 = {name: "another assigned name"}

// print objects
console.log(object1.name); //outputs 'assigned name'
console.log(object2.name); //outputs 'another assigned name' ?????

If you can understand the above code, you now can understand the difference between exports and module.exports. In above code, both object1 and object2 points to the same reference. If I change the contents of anyone of the objects, it will reflect in both but If I change the object itself it will not reflect. That means mutation will change all objects having same reference but assignment will change only one object

That means both exports and module.exports references to the same empty objects. Remember that : module.exports and NOT exports will be returned from your module when you requiring that module from somewhere else.  So in above example if we replace module.exports with exports, changes will not reflect to module.exports (returning object) and therefore doesn't work.

To further illustrate this, lets consider following example 1

exports = function() {
 console.log('with exports only');
}

when we assign function to exports. Following will be the result of exports and module.exports


exports = [function],
module.exports = {}

Since module.exports will be returned, this will return empty object. So this doesn't work.

Now, consider example 2

exports.a = function () {
  console.log('exports with mutation');
}

When we mutate exports, following will be the result of exports and module.exports

exports = {a: [function]},
module.exports = {a: [funciton]}
This will return non-empty object. So thi
s works

Finally, consider example 3

module.exports = function() {
    console.log('with  module.exports only');
}

When we assign function to module.exports, following will be the contents of exports and
module.exports.

exports = {},
module.exports = [funciton]
This will return non-empty object. So this also works.

No comments:

Post a Comment