Node.js实现一个简单的Web MVC框架---我要看明白的第一个mvc
Node.js是什么
Node让你可以用javascript编写服务器端程序,让javascript脱离web浏览器的限制,像C#、JAVA、Python等语言一样在服务器端运行,这也让一些熟悉Javascript的前端开发人员进军到服务器端开发提供了一个便利的途径。 Node是基于Google的V8引擎封装的,并提供了一些编写服务器程序的常用接口,例如文件流的处理。Node的目的是提供一种简单的途径来编写高性能的网络程序。
(注:1、本文基于Node.js V0.3.6; 2、本文假设你了解JavaScript; 3、本文假设你了解MVC框架;4、本文示例源代码:learnNode.zip)
Node.js的性能
hello world 测试:
300并发请求,返回不同大小的内容:
为什么node有如此高的性能?看node的特性。
Node.js的特性
1. 单线程
ache是什么意思
西雅图不眠夜 2. 非阻塞IO
3. Google V8翰林学院
4. 事件驱动
更详细的了解node请看淘宝UED博客上的关于node.js的一个幻灯片:
你好,世界
这,当然是俗套的Hello World啦(hello_world.js):
var http = require('http'); ateServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); d('Hello World\n'); }).listen(8124, ""); console.log('Server running at .1:8124/');
require类似于C#的using、Python的import,用于导入模块(module)。node使用的是Com
avenuesmonJS的模块系统。ateServer 的参数为一个函数,每当有新的请求进来的时候,就会触发这个函数。最后就是绑定要监听的端口。
怎么运行?
当然,是先安装node.js啦。到,支持Linux、Mac,也支持windows下的Cygwin。具体的安装说明见: 装好node后,就可以运行我们的hello world了:
$ node hello_world.js Server running at .1:8124/
编程习惯的改变?
我们来写一个读取文件内容的脚本:
//output_me.js var fs = require('fs'), fileContent = 'nothing'; fs.readFile(__filename, "utf-8", function(err, file) { if(err) { console.log(err); return; } fileContent = file; console.log('end readfile \n'); }); console.log('doSomethingWithFile: '+ fileContent +'\n');
这个脚本读取当前文件的内容并输出。__filename是node的一个全局变量,值为当前文件的绝对路径。我们执行这个脚本看一下:
modesty>克林顿演讲
有没发现结果不对呢?打印的fileContent并不是读取到的文件内容,而是初始化的时候赋值的nothing,并且‘end readfile’最后才打印出来。前面我们提到node的一个特性就是非阻塞IO,而readFile就是异步非阻塞读取文件内容的,所以后面的代码并不会等到文件内容读取完了再执行。请谨记node的异步非阻塞IO特性。所以我们需要将上面的代码修改为如下就能正常工作了:
//output_me.js var fs = require('fs'), fileContent = 'nothing'; fs.readFile(__filename, "utf-8", function(err, file) { if(err) { console.log(err); return; } fileContent = file; //对于file的处理放到回调函数这里处理 console.log('doSomethingWithFile: '+ fileContent +'\n'); }); console.log('我们先去喝杯茶\n');
写个Web MVC框架试试
下面我们用node来写一个小玩具:一个Web MVC框架。这个小玩具我称它为n2Mvc,它的代码结构看起来大概如下:
academically
anachronism
和hello world一样,我们需要一个http的服务器来处理所有进来的请求:
var http = require('http'), querystring = require("querystring"); exports.runServer = functio
n(port){ port = port || 8080; var rver = ateServer(function(req, res){ var _postData = ''; //on用于添加一个监听函数到一个特定的事件 ('data', function(chunk) { _postData += chunk; }) .on('end', function() { req.post = querystring.par(_postData); handlerRequest(req, res); }); }).listen(port); console.log('Server running at .1:'+ port +'/'); };
这里定义了一个runServer的方法来启动我们的n2Mvc的服务器。有没注意到runServer前面有个exports?这个exports相当于C#中的publish,在用require导入这个模块的时候,runServer可以被访问到。我们写一个脚本来演示下node的模块导入系统:
//moduleExample.js var myPrivate = '艳照,藏着'; Publish = '冠西的相机'; Publish2 = 'this也可以哦'; console.log('moduleExample.js loaded \n');
执行结果:
圣诞节电影
wake island
从结果中我们可以看出exports和this下的变量在外部导入模块后,可以被外部访问到,而var定义的变量只能在脚本内部访问。 从结果我们还可以看出,第二次require导入moduleExample模块的时候,并没有打印“moduleExample.js loaded”,因为require导入模块的时候,会先从require.cache 中检查模块是否已经加载,如果没有加载,才会从硬盘中查找模块脚本并加载。 require支持相对路径查找模块,例如上面代码中require(‘./moduleExample’)中的“./”就代表在当前目录下查找。如果不是相当路径,例如 require(‘http’),node则会到require.paths中去查找,例如我的系统require.paths为:
当require(‘http’)的时候,node的查找路径为:
1、/home/qleelulu/.node_modules/http 2、/home/qleelulu/.node_modules/http.js 3、/home/qleelulu/.node_de 4、/home/qleelulu/.node_modules/http/index.js 5、/home/qleelulu/.node_modules/de 6、/home/qleelulu/.node_libraries/http 7、/home/qleelulu/.node_libraries/http.js 8、参考前面
再看回前面的代码,ateServer中的回调函数中的request注册了两个事件,前面提到过node的一个特点是事件驱动的,所以这种事件绑定你会到处看到(想想jQuery的事件绑定?例如$(‘a’).click(fn))。关于node的事件我们在后面再细说。request对象的data事件会在接收客户端post上来的数据时候触发,而end事件则会在最后触发。所以我们在data事件里面处理接收到的数据(例如post过来的form表单数据),在end事件里面通过handlerRequest
函数来统一处理所有的请求并分发给相应的controller action处理。 handlerRequest的代码如下:
var route = require('./route'); var handlerRequest = function(req, res){ //通过route来获取controller和action信息 var actionInfo = ActionInfo(req.url, hod); //如果route中有匹配的action,则分发给对应的action if(actionInfo.action){ //假设controller都放到当前目录的controllers目录里面,还记得require是怎么搜索module的么? var controller = require('./controllers/'+ller); // ./controllers/blog if(controller[actionInfo.action]){ var ct = new controllerContext(req, res); //动态调用,动态语言就是方便啊 //通过apply将controller的上下文对象传递给action controller[actionInfo.action].apply(ct, actionInfo.args); }el{ handler500(req, res, 'Error: controller "' + ller + '" without action "' + actionInfo.action + '"') } }el{ //如果route没有匹配到,则当作静态文件处理 staticFileServer(req, res); } };
这里导入来一个route模块,route根据请求的url等信息去获取获取controller和action的信息,如果获取到,则通过动态调用调用action方法,如果没有匹配的action信息,则作为静态文件处理。 下面是route模块的代码: