Node.js学习笔记(二)—— 模块化

欢迎转载,但请注明出处:http://blog.csdn.net/sysuzjz/article/details/43987289

鸣谢:nodebeginner.org

一个应用由不同模块组成,现在我们就将这几个模块一一道来。

服务器模块

上一节中,我们用了一个使用Node的例子:
var http = require("http");

http.createServer(function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}).listen(8888);
 这段代码用于开启一个服务器。http是一个内置的模块,第一行代码使得本地变量http赋值为http模块对象。这也是最核心的模块。 
JavaScript有一个特定,就是一切皆对象,函数作为对象的一种,也可以作为函数的参数。我们把其中的匿名回调函数替换成实名函数。这样,我们就可以把上面那个例子的耦合度再度降低。
var http = require("http");
function onRequest(request, response) {
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
}
http.createServer(onRequest).listen(8080);
把这段代码放到server.js里,运行node server.js,结果和之前是一模一样的。
再进一步处理,我们把函数进一步封装,把服务器开启包装成一个函数。
var http = require("http");

function start() {
    function onRequest(request, response) {
        console.log("Request received.");
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }

    http.createServer(onRequest).listen(8888);
}

start();

有一点值得注意的是,大部分服务器在访问http://localhost:8888的时候,会顺便访问http://localhost:8888/favicon.ico。所以,回调函数可能会被执行两次。

全局模块

上述服务器模块功能有限,或者说,我们刻意对上述模块进行限制,使其只负责HTTP服务器方面,而不涉及具体业务处理。为了让各模块各自独立,降低耦合度,我们应该设定一个全局的模块,用来各模块之间通信协同。我们把它命名为index.js,内容如下:
var server = require("./server");

server.start();
第一句表示包含了本目录里的server.js里的模块。这样,就只需要通过运行index.js,就能达到我们之前的效果。但是,我们还得先对server.js作点修改来适应这种变化。
var http = require("http");
function start() {
    function onRequest(request, response) {
        console.log("Request received.");
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }
    http.createServer(onRequest).listen(8888);
}
exports.start = start;
我们可以看到,其实变化的就只有最后一句,exports是本文件,或者说,本模块的返回对象,给exports对象添加start方法。这是因为start函数是不能被其他模块直接引用的,但是模块返回对象可以通过require方法暴露在其他模块里。这样,就达到了在全局模块调用其他模块的目的。
我们运行node index.js,会发现,结果和上面的还是一模一样。

路由模块

一般情况下,我们的应用会有多种业务需求,如何通过url,来识别不同请求呢?这就是路由。
不像PHP,PHP不负责服务器部分,所以PHP可以一个文件负责一个业务,请求也是PHP的文件名,例如action="./index.php"。但Node不同,服务器也是由Node来搭建,而且集中在server.js中。因此,我们引入了路由机制。即所有同域下的请求,根据路径来转交给不同的处理函数,这种做法类似于路由器。
我们来介绍两个新的模块:url和querystring,它们负责url的解析。同http模块类似,它们也是内置的模块,所以我们不需要去安装它们。
我们以一个url为例,介绍下各部分和这两个模块的对应关系

接下来我们来编写路由模块。编写router.js文件:
function route(pathname) {
    console.log("About to route a request for " + pathname);
}

exports.route = route;
将路由穿插进服务器模块中。这是因为url来自于createServer回调函数里的request对象。
var http = require("http");
var url = require("url");

function start() {
    function onRequest(request, response) {
        var pathname = url.parse(request.url).pathname;
        console.log("Request for " + pathname + " received.");
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }
    http.createServer(onRequest).listen(8888);
}
exports.start = start;
怎么将两者协同起来呢?这就要靠全局模块了。
我们先将server.js修改一下,
var http = require("http");
var url = require("url");
function start(route) {
    function onRequest(request, response) {
        var pathname = url.parse(request.url).pathname;
        route(pathname);
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }
    http.createServer(onRequest).listen(8888);
}
注意,start函数多出了一个参数,而这个参数在onRequest函数中得到调用,说明这是一个函数,并且参数为路径字符串。而这个函数,其实就是路由模块的返回对象。
我们修改下index.js:
var server = require("./server");
var router = require("./router");

server.start(router.route);
这样,我们又加入了路由模块。并且,服务器依旧只干服务器的活。当然,如果你觉得url的解析不应该放在服务器模块的话,你也可以把request对象当做参数传给route函数,然后在router模块中进行解析。

业务处理模块

我们虽然加入了路由模块,但实际上,它什么都没做。所以,我们还需要一个来做事情的模块,那就是业务处理模块。路由模块根据请求url来决定,交由哪个业务处理函数来执行。假设我们有start和upload两个业务。编写handler.js:
function start() {
    console.log("Request handler 'start' was called.");
}
function upload() {
    console.log("Request handler 'upload' was called.");
}

exports.start = start;
exports.upload = upload;
可能有些强迫症患者已经发现,如果业务较多的话,下面的exports.xx = xx会异常的多,这对于一个强迫症患者来说简直不能忍,也给维护带来了困难。我们包装一下:
function start() {
    console.log("Request handler 'start' was called.");
}
function upload() {
    console.log("Request handler 'upload' was called.");
}
var exportObj = {
    start: start,
    upload: upload
};
exports = exportObj;
瞬间高大上了。
那么,问题来了,怎么将业务处理模块并入呢?同样的,还是得靠全局模块。修改index.js
var server = require("./server");
var router = require("./router");
var handler = require("./handler");

handlers['/'] = handlers['start'];

server.start(router.route, handler);
似乎不难理解,唯一的问题是,start函数又多出了一个参数,这个参数并不是服务器模块所需要的,但却是路由模块所需要的,而路由模块是在服务器模块中调用的。所以,我们间接的将业务处理模块,通过服务器模块,传递给路由模块。修改server.js:
function start(route, handle) {
    function onRequest(request, response) {
        var pathname = url.parse(request.url).pathname;
        console.log("Request for " + pathname + " received.");
        route(handle, pathname);

        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }
    http.createServer(onRequest).listen(8888);
}

exports.start = start;
然后修改router.js:
function route(handle, pathname) {
    console.log("About to route a request for " + pathname);
    if (typeof handle[pathname] === 'function') {
        handle[pathname]();
    } else {
        console.log("No request handler found for " + pathname);
    }
}
exports.route = route;

最终形态

这样似乎就完美了。但还有个小问题,不同业务处理函数也许输出不一样,我们总不能在每个业务处理函数中都return要输出的东西,然后交给服务器模块去输出吧?毕竟,输出靠的是createServer回调函数的response对象。更好的办法是,将response对象传给业务处理模块。那么,server.js的最终形态就是:
var http = require("http");
var url = require("url");
function start(route, handler) {
    function onRequest(request, response) {
        var pathname = url.parse(request.url).pathname;
        route(pathname, handler, response);
        response.writeHead(200, {"Content-type": "text/plain"});
        response.write("hello world");
        response.end();
    }
    http.createServer(onRequest).listen(8080);
}
exports.start = start;
而路由模块也相应的做点更改(其实就是传个参数,酱油了一次),router.js最终形态如下:
function route(handle, pathname, response) {
    console.log("About to route a request for " + pathname);
    if (typeof handle[pathname] === 'function') {
        handle[pathname](response);
    } else {
        console.log("No request handler found for " + pathname);
    }
}
exports.route = route;
业务处理模块handler.js最终如下:
function start(response) {
    response.writeHead(200, {"Content-type": "text/plain"});
    response.write("Request handler 'start' was called.");
    response.end();
}
function upload(response) {
    response.writeHead(200, {"Content-type": "text/plain"});
    response.write("Request handler 'upload' was called.");
    response.end();
}
var exportObj = {
    start: start,
    upload: upload
};
exports = exportObj;
可能又会有强迫症患者发现了,部分代码是可以复用的,我们继续包装:
function output(response, message) {
    response.writeHead(200, {"Content-type": "text/plain"});
    response.write(message);
    response.end();
}
function start(response) {
    output(response, "Request handler 'start' was called.");
}
function upload(response) {
    output(response, "Request handler 'upload' was called.");
}
var exportObj = {
    start: start,
    upload: upload
};
module.exports = exportObj;
这样就告一段落了。

以上只是一些简单的模块,并没有多少实际内容,具体内容还是得根据需要来进行填充。也许这种思路不是最佳的,但也并不是一文不值,至少,它体现了一种功能分离、低耦合的思想。如果大家有更好的建议,或者发现不足、错误之处,欢迎在评论里提出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值