NodeJS学习笔记: require, exports 和 module.exports 的初印象

691 查看

前言

本人不是技术专家,该笔记只是从使用语言进行开发的层面上记录一些体会,不包含也不想尝试从源码或者更深的层次去讨论语言本身的优劣。文章内容是笔者的个人感悟,既不保证正确性,也不保证别人能看懂。

这是该笔记的第一篇,虽然不确定以后会不会有第二篇。

引子

最近不在项目里,所以打算趁此机会了解一下MEANJS架构,磕磕绊绊的配置好环境之后就尝试开始熟悉它了。学习的捷径就是模仿,这是我的个人经验,但是模仿之前总要先看明白示例代码吧,结果悲剧的发现,我被随处可见的 require, exports 和 module.exports 搞晕了,又是一通各种搜索前人经验的过程,然后终于有点明白了,特此记录下来。

正文

require方法按照一定的规则(规则见附录)去寻找传入的参数对应的js文件,这个文件必须为exports或者module.exports赋值,这个值会被作为require方法的返回值传递给调用者。

示例代码 main.js

'use strict';
var me = require('./module.js');
console.log(me); //Hello require!

示例代码 module.js

'use strict';
exports = module.exports = 'Hello require!';
// 也可以写成 exports = 'Hello require!';
// 也可以写成 module.exports = 'Hello require!';
// 但是通常还是使用 exports = module.exports = something 这种格式

exports和module.exports有细微的区别,可以参考这篇文章《nodejs中exports与module.exports的区别》,这里不多说。

从另一个角度来说,require模拟了其他语言的面向对象开发机制,我们可以在一个文件中声明对象,然后用require来引用,只要记得在声明对象的结尾写上

exports = module.exports = yourClass;

就可以了。下面是一个扩展的示例。

//demo.js
'use strict';
var mT = require('./test.js');
var mT1 = new mT(); //此时a = 5
mT1.e(); //此时a = 6
var mT2 = new mT();
console.log(mT1.d()); //6
console.log(mT1.d()); //7
console.log(mT1.d()); //8
console.log(mT2.d()); //7
console.log(mT2.d()); //8
//test.js
'use strict';
var a = 5;
var b = function(){
    this.c = a;
    this.d = function() {
        this.c ++;
        return this.c;
    };
    this.e = function() {
        a++;
    };
    return this;
};
exports = module.exports = b;

以上test.js中的b是对象的声明,a可以理解为一个静态变量,因此mT1修改了a的值之后导致mT2中c的初始值是6而不再是5,而由于变量作用域的关系,mT1中的c的值的变化并不会对mT2中的c的值造成影响。理解这一点之后,我们就可以自如的声明出普通对象、单件对象,以及对访问域进行区别(公开的或者受保护的),同样的也可以用来模拟抽象方法,如

var o = function() {};
o.prototype.getName = function() {
    throw "请在子类中实现该方法!";
};
var oc = function() {};
oc.prototype = new o();
oc.prototype.getName = function() {
    console.log('yes');
};
var t1 = new oc();
t1.getName(); // yes
var t2 = new o();
t.getName(); // exception

虽然这个做法看起来很傻,但是确实可以帮到我们。

结语

现在,我终于能比较流畅的去阅读示例的源码了,相信这是一个好的开始。阅读源码其实是一件很有趣的事情,你可以先去推测作者的思路,然后看看自己是不是猜对了,如果猜对了,那么我可以节约很多时间(既然思路一样,那么具体的实现方式其实不是很重要),如果猜错了,想一下他为什么这么做,补益自身。

附录

  1. require方法寻址规则

    require(X) from module at path Y
    1. If X is a core module,
       a. return the core module
       b. STOP
    2. If X begins with './' or '/' or '../'
       a. LOAD_AS_FILE(Y + X)
       b. LOAD_AS_DIRECTORY(Y + X)
    3. LOAD_NODE_MODULES(X, dirname(Y))
    4. THROW "not found"
    
    LOAD_AS_FILE(X)
    1. If X is a file, load X as JavaScript text.  STOP
    2. If X.js is a file, load X.js as JavaScript text.  STOP
    3. If X.json is a file, parse X.json to a JavaScript Object.  STOP
    4. If X.node is a file, load X.node as binary addon.  STOP
    
    LOAD_AS_DIRECTORY(X)
    1. If X/package.json is a file,
       a. Parse X/package.json, and look for "main" field.
       b. let M = X + (json main field)
       c. LOAD_AS_FILE(M)
    2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
    3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
    4. If X/index.node is a file, load X/index.node as binary addon.  STOP
    
    LOAD_NODE_MODULES(X, START)
    1. let DIRS=NODE_MODULES_PATHS(START)
    2. for each DIR in DIRS:
       a. LOAD_AS_FILE(DIR/X)
       b. LOAD_AS_DIRECTORY(DIR/X)
    
    NODE_MODULES_PATHS(START)
    1. let PARTS = path split(START)
    2. let I = count of PARTS - 1
    3. let DIRS = []
    4. while I >= 0,
       a. if PARTS[I] = "node_modules" CONTINUE
       c. DIR = path join(PARTS[0 .. I] + "node_modules")
       b. DIRS = DIRS + DIR
       c. let I = I - 1
    5. return DIRS