文件Nodejs实现一个简单的WebMVC框架我要看明白的第一个mvc.docx
- 文档编号:9339391
- 上传时间:2023-02-04
- 格式:DOCX
- 页数:10
- 大小:129.16KB
文件Nodejs实现一个简单的WebMVC框架我要看明白的第一个mvc.docx
《文件Nodejs实现一个简单的WebMVC框架我要看明白的第一个mvc.docx》由会员分享,可在线阅读,更多相关《文件Nodejs实现一个简单的WebMVC框架我要看明白的第一个mvc.docx(10页珍藏版)》请在冰豆网上搜索。
文件Nodejs实现一个简单的WebMVC框架我要看明白的第一个mvc
【关键字】文件
Node.js实现一个简单的WebMVC框架---我要看明白的第一个mvc
Node.js是什么
Node让你可以用javascript编写服务器端程序,让javascript脱离web浏览器的限制,像C#、JAVA、Python等语言一样在服务器端运行,这也让一些熟悉Javascript的前端开发人员进军到服务器端开发提供了一个便利的途径。
Node是基于Google的V8引擎封装的,并提供了一些编写服务器程序的常用接口,例如文件流的处理。
Node的目的是提供一种简单的途径来编写高性能的网络程序。
(注:
1、本文基于Node.jsV0.3.6;2、本文假设你了解JavaScript;3、本文假设你了解MVC框架;4、本文示例源代码:
learnNode.zip)
Node.js的性能
helloworld测试:
300并发请求,返回不同大小的内容:
为什么node有如此高的性能?
看node的特性。
Node.js的特性
1.单线程
2.非阻塞IO
3.GoogleV8
4.事件驱动
更详细的了解node请看淘宝UED博客上的关于node.js的一个幻灯片:
你好,世界
这,当然是俗套的HelloWorld啦(hello_world.js):
varhttp=require('http');http.createServer(function(req,res){res.writeHead(200,{'Content-Type':
'text/plain'});res.end('HelloWorld\n');}).listen(8124,"");console.log('Serverrunningat.1:
8124/');
require类似于C#的using、Python的import,用于导入模块(module)。
node使用的是CommonJS的模块系统。
http.createServer的参数为一个函数,每当有新的请求进来的时候,就会触发这个函数。
最后就是绑定要监听的端口。
怎么运行?
当然,是先安装node.js啦。
到,支持Linux、Mac,也支持windows下的Cygwin。
具体的安装说明见:
装好node后,就可以运行我们的helloworld了:
$nodehello_world.jsServerrunningat.1:
8124/
编程习惯的改变?
我们来写一个读取文件内容的脚本:
//output_me.jsvarfs=require('fs'),fileContent='nothing';fs.readFile(__filename,"utf-8",function(err,file){if(err){console.log(err);return;}fileContent=file;console.log('endreadfile\n');});console.log('doSomethingWithFile:
'+fileContent+'\n');
这个脚本读取当前文件的内容并输出。
__filename是node的一个全局变量,值为当前文件的绝对路径。
我们执行这个脚本看一下:
有没发现结果不对呢?
打印的fileContent并不是读取到的文件内容,而是初始化的时候赋值的nothing,并且‘endreadfile’最后才打印出来。
前面我们提到node的一个特性就是非阻塞IO,而readFile就是异步非阻塞读取文件内容的,所以后面的代码并不会等到文件内容读取完了再执行。
请谨记node的异步非阻塞IO特性。
所以我们需要将上面的代码修改为如下就能正常工作了:
//output_me.jsvarfs=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');
写个WebMVC框架试试
下面我们用node来写一个小玩具:
一个WebMVC框架。
这个小玩具我称它为n2Mvc,它的代码结构看起来大概如下:
和helloworld一样,我们需要一个http的服务器来处理所有进来的请求:
varhttp=require('http'),querystring=require("querystring");exports.runServer=function(port){port=port||8080;varserver=http.createServer(function(req,res){var_postData='';//on用于添加一个监听函数到一个特定的事件req.on('data',function(chunk){_postData+=chunk;}).on('end',function(){req.post=querystring.parse(_postData);handlerRequest(req,res);});}).listen(port);console.log('Serverrunningat.1:
'+port+'/');};
这里定义了一个runServer的方法来启动我们的n2Mvc的服务器。
有没注意到runServer前面有个exports?
这个exports相当于C#中的publish,在用require导入这个模块的时候,runServer可以被访问到。
我们写一个脚本来演示下node的模块导入系统:
//moduleExample.jsvarmyPrivate='艳照,藏着';exports.myPublish='冠西的相机';this.myPublish2='this也可以哦';console.log('moduleExample.jsloaded\n');
执行结果:
从结果中我们可以看出exports和this下的变量在外部导入模块后,可以被外部访问到,而var定义的变量只能在脚本内部访问。
从结果我们还可以看出,第二次require导入moduleExample模块的时候,并没有打印“moduleExample.jsloaded”,因为require导入模块的时候,会先从require.cache中检查模块是否已经加载,如果没有加载,才会从硬盘中查找模块脚本并加载。
require支持相对路径查找模块,例如上面代码中require(‘./moduleExample’)中的“./”就代表在当前目录下查找。
如果不是相当路径,例如require(‘http’),node则会到require.paths中去查找,例如我的系统require.paths为:
当require(‘http’)的时候,node的查找路径为:
1、/home/qleelulu/.node_modules/http2、/home/qleelulu/.node_modules/http.js3、/home/qleelulu/.node_modules/http.node4、/home/qleelulu/.node_modules/http/index.js5、/home/qleelulu/.node_modules/http/index.node6、/home/qleelulu/.node_libraries/http7、/home/qleelulu/.node_libraries/http.js8、参考前面
再看回前面的代码,http.createServer中的回调函数中的request注册了两个事件,前面提到过node的一个特点是事件驱动的,所以这种事件绑定你会到处看到(想想jQuery的事件绑定?
例如$(‘a’).click(fn))。
关于node的事件我们在后面再细说。
request对象的data事件会在接收客户端post上来的数据时候触发,而end事件则会在最后触发。
所以我们在data事件里面处理接收到的数据(例如post过来的form表单数据),在end事件里面通过handlerRequest函数来统一处理所有的请求并分发给相应的controlleraction处理。
handlerRequest的代码如下:
varroute=require('./route');varhandlerRequest=function(req,res){//通过route来获取controller和action信息varactionInfo=route.getActionInfo(req.url,req.method);//如果route中有匹配的action,则分发给对应的actionif(actionInfo.action){//假设controller都放到当前目录的controllers目录里面,还记得require是怎么搜索module的么?
varcontroller=require('./controllers/'+actionInfo.controller);//./controllers/blogif(controller[actionInfo.action]){varct=newcontrollerContext(req,res);//动态调用,动态语言就是方便啊//通过apply将controller的上下文对象传递给actioncontroller[actionInfo.action].apply(ct,actionInfo.args);}else{handler500(req,res,'Error:
controller"'+actionInfo.controller+'"withoutaction"'+actionInfo.action+'"')}}else{//如果route没有匹配到,则当作静态文件处理staticFileServer(req,res);}};
这里导入来一个route模块,route根据请求的url等信息去获取获取controller和action的信息,如果获取到,则通过动态调用调用action方法,如果没有匹配的action信息,则作为静态文件处理。
下面是route模块的代码:
varparseURL=require('url').parse;//根据http请求的method来分别保存route规则varroutes={get:
[],post:
[],head:
[],put:
[],delete:
[]};/***注册route规则*示例:
*route.map({*method:
'post',*url:
/\/blog\/post\/(\d+)\/?
$/i,*controller:
'blog',*action:
'showBlogPost'*})*/exports.map=function(dict){if(dict&&dict.url&&dict.controller){varmethod=dict.method?
:
'get';routes[method].push({u:
dict.url,//url匹配正则c:
dict.controller,a:
dict.action||'index'});}};exports.getActionInfo=function(url,method){varr={controller:
null,action:
null,args:
null},method=method?
method.toLowerCase():
'get',//url:
/blog/index?
page=1,则pathname为:
/blog/indexpathname=parseURL(url).pathname;varm_routes=routes[method];for(variinm_routes){//正则匹配r.args=m_routes[i].u.exec(pathname);if(r.args){r.controller=m_routes[i].c;r.action=m_routes[i].a;;//第一个值为匹配到的整个url,去掉break;}}//如果匹配到route,r大概是{controller:
'blog',action:
'index',args:
['1']}returnr;};
map方法用于注册路由规则,我们新建一个config.js的文件,来配置route规则:
//config.jsvarroute=require('./route');route.map({method:
'get',url:
/\/blog\/?
$/i,controller:
'blog',action:
'index'});
如果请求的url有匹配的route规则,则会返回controller和action信息。
例如上面的route配置,当访问/blog这个url的时候,则会调用./controllers/blog.js模块里面的index函数。
当调用action的时候,会传递controllerContext给acation:
varct=newcontrollerContext(req,res);//动态调用,动态语言就是方便啊//通过apply将controller的上下文对象传递给actioncontroller[actionInfo.action].apply(ct,actionInfo.args);
这里会通过apply将controllerContext作为action的this,并传递args作为action的参数来调用action。
ontrollerContext封装了一些action会用到的方法:
//controller的上下文对象varcontrollerContext=function(req,res){this.req=req;this.res=res;this.handler404=handler404;this.handler500=handler500;};=function(viewName,context){viewEngine.render(this.req,this.res,viewName,context);};=function(json){viewEngine.renderJson(this.req,this.res,json);};
在action中处理完逻辑获取获取到用户需要的数据后,就要呈现给用户。
这就需要viewEngine来处理了。
ViewEngine的代码如下:
varviewEngine={render:
function(req,res,viewName,context){varfilename=path.join(__dirname,'views',viewName);try{varoutput=Shotenjin.renderView(filename,context);}catch(err){handler500(req,res,err);return;}res.writeHead(200,{'Content-Type':
'text/html'});res.end(output);},renderJson:
function(res,json){//TODO:
}};
这里viewEngine主要负责模板解析。
node有很多的可用的模块,模板解析模块也有一大堆,不过这里我们是要“玩”,所以模板解析系统我们这里使用jstenjin来稍作修改:
//shotenjin.js增加的代码//模板缓存,缓存解析后的模板Shotenjin.templateCatch={};//读取模板内容//在模板中引用模板使用:
{#../layout.html#}Shotenjin.getTemplateStr=function(filename){//console.log('gettemplate:
'+filename);vart='';//这里使用的是同步读取if(path.existsSync(filename)){t=fs.readFileSync(filename,'utf-8');}else{throw'View:
'+filename+'notexists';}t=t.replace(/\{#[\s]*([\.\/\w\-]+)[\s]*#\}/ig,function(m,g1){varfp=path.join(filename,g1.trim())returnShotenjin.getTemplateStr(fp);});returnt;};Shotenjin.renderView=function(viewPath,context){vartemplate=Shotenjin.templateCatch[viewPath];if(!
template){vartemplate_str=Shotenjin.getTemplateStr(viewPath);vartemplate=newShotenjin.Template();template.convert(template_str);//添加到缓存中Shotenjin.templateCatch[viewPath]=template;}varoutput=template.render(context);returnoutput;};global.Shotenjin=Shotenjin;
增加的代码主要是读取模板的内容,并解析模板中类似{#../layout.html#}的标签,递归读取所有的模板内容,然后调用jstenjin的方法来解析模板。
这里读取文件内容使用的是fs.readFileSync,这是同步阻塞读取文件内容的,和我们平时使用的大多编程语言一样,而fs.readFile的非阻塞异步读。
这里的shotenjin.js原来是给客户端web浏览器javascript解析模板用的,现在拿到node.js来用,完全不用修改就正常工作。
GoogleV8真威武。
现在基本的东西都完成了,但是对于静态文件,例如js、css等我们需要一个静态文件服务器:
varstaticFileServer=function(req,res,filePath){if(!
filePath){filePath=path.join(__dirname,config.staticFileDir,url.parse(req.url).pathname);}path.exists(filePath,function(exists){if(!
exists){handler404(req,res);return;}fs.readFile(filePath,"binary",function(err,file){if(err){handler500(req,res,err);return;}varext=path.extname(filePath);ext=ext?
ext.slice
(1):
'html';res.writeHead(200,{'Content-Type':
contentTypes[ext]||'text/html'});res.write(file,"binary");res.end();});});};varcontentTypes={"aiff":
"audio/x-aiff","arj":
"application/x-arj-compressed"//省略}
简单来说就是读取文件内容并写入到response中返回给客户端。
现在该有的都有了,我们写一个action:
//./controllers/blog.jsexports.index=function(){this.render('blog/index.html',{msg:
'HelloWorld'});};
blog/index.html的内容为:
{#../../header.html#}
n2MvcDemo
#{msg}
{#../../footer.html#}
接着,就是写一个脚本来启动我们的n2Mvc了:
//run.jsvarn2MvcServer=require('./server');n2MvcServer.runServer();
ok,运行我们的启动脚本:
在浏览器访问看看:
嗯嗯,一切正常。
好,接下来我们再写一个获取新浪微博最新微博的页面。
首先,我们在config.js中增加一个route配置:
route.map({method:
'get',url:
/\/tweets\/?
$/i,controller:
'blog',action:
'tweets'});
然后开始写我们的cnotrolleraction:
varhttp=require('http'),events=require("events");vartsina_client=http.createClient(80,"");vartweets_emitter=newevents.EventEmitter();//action:
tweetsexports.tweets=function(blogType){var_t=this;varlistener=tweets_emitter.once("tweets",function(tweets){_t.render('blog/tweets.html',{tweets:
tweets});});get_tweet
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 文件 Nodejs 实现 一个 简单 WebMVC 框架 明白 第一 mvc