有時(shí)候面試,老是被問(wèn)到一些自己使用過(guò)的技術(shù),但是僅僅限於使用,這個(gè)是真沒(méi)深入研究過(guò),鄙人能力有限的緣故吧。但是呢,每每被問(wèn)倒之後下來(lái)又會(huì)想為什麼不在深入一點(diǎn)呢?(邪惡??之笑)~~;完全可以再多了解一點(diǎn)嘛。
不過(guò)怎麼說(shuō)呢,面試官需要的是一類人才,他也會(huì)問(wèn)到旁系不相干或者在深層次的問(wèn)題,這點(diǎn)我就覺(jué)得有點(diǎn)奇怪了,甲方需要的能力滿足,關(guān)於其他的不會(huì)反而會(huì)減分,難道不應(yīng)該是,不會(huì)不要緊,有時(shí)間多學(xué)習(xí)學(xué)習(xí)就好了嗎。非要搞得難倒面試者才甘心。既然這樣的面試官很多,所以能多學(xué)點(diǎn)是一點(diǎn),高標(biāo)準(zhǔn)要求自己。
那天是阿里的面試官電話面試的,也僅限于溝通,額~~,被阿里電話面試了好多次,都沒(méi)結(jié)果,我想說(shuō)的是遲早我都會(huì)進(jìn)來(lái),浪費(fèi)大家時(shí)間幹嘛。
前幾個(gè)問(wèn)題還好,都是回答自己擅長(zhǎng)的問(wèn)題,還算OK。我的站點(diǎn)有一個(gè)是花夏集。他是看到這個(gè)聯(lián)繫到我的。一上來(lái)就問(wèn)我是不是 花夏集 搞得有點(diǎn)一頭霧水。然後就解釋是怎麼看到我的,找到我的。瞬間很奇怪加驚動(dòng)。對(duì)!就是驚動(dòng)。居然有人因?yàn)榭吹揭粋€(gè)技術(shù)人員的詩(shī)集站點(diǎn)從而打電話進(jìn)行面試的。可能是由於這個(gè)站點(diǎn)時(shí)nodejs+express+mongodb做的吧。事實(shí)證明確實(shí)是因?yàn)檫@樣,他們剛好需要nodejs方面的工程師。
巴拉巴拉...聊了一些,最後聊到koa中間件,這個(gè)我不會(huì),koa沒(méi)用過(guò),用過(guò)express.僅限於參照文檔做的,然後就開(kāi)始了......對(duì)於express深入的確實(shí)不了解,聞到中間件事一問(wèn)三不知,只知道怎麼用,簡(jiǎn)單的自己實(shí)現(xiàn)原理都不清楚,所以現(xiàn)在才開(kāi)始寫(xiě)一篇文章來(lái)說(shuō)明下。也是看了網(wǎng)上的文章解釋,有了進(jìn)一步的了解。
廢話了這麼多,進(jìn)入正題:express中間件原理簡(jiǎn)單剖析
學(xué)express首先得會(huì)點(diǎn)nodejs吧,先來(lái)一段nodejs最基礎(chǔ)的hello world
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
上面這段代碼來(lái)自nodejs的官網(wǎng),非常簡(jiǎn)單,就是來(lái)一個(gè)請(qǐng)求,就用傳給createServer的匿名函數(shù)來(lái)處理請(qǐng)求。
繼續(xù)看看Express的代碼
var app = express();
//...中間忽略
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
對(duì)比可以看出,執(zhí)行express()后,會(huì)返回一個(gè)函數(shù),賦值給app,app應(yīng)該是:
function(req,res){//...}
然后請(qǐng)求都會(huì)被app這個(gè)函數(shù)處理(因?yàn)檫@個(gè)app是執(zhí)行express后的結(jié)果)
可以認(rèn)為,在express內(nèi)部,有一個(gè)函數(shù)的數(shù)組,暫時(shí)叫這個(gè)數(shù)組tasks,每來(lái)一個(gè)請(qǐng)求express內(nèi)部會(huì)依次執(zhí)行這個(gè)數(shù)組中的函數(shù)(這里說(shuō)依次并不嚴(yán)謹(jǐn),每個(gè)函數(shù)必須滿足一定條件才行,這個(gè)后面說(shuō)),應(yīng)該可以想到,在這個(gè)函數(shù)數(shù)組里,每個(gè)函數(shù)的簽名應(yīng)該像下面那樣實(shí)際上這個(gè)就是中間件的精髓所在...噓!
function(req, res){//...}
實(shí)際上應(yīng)該是:
function(req, res, next){//...}
這里的next,是指下一個(gè)函數(shù)。后面我們會(huì)寫(xiě)一些試驗(yàn)來(lái)體驗(yàn)一下這個(gè)next.
1.導(dǎo)入相關(guān)模塊
2.執(zhí)行過(guò)
var app = express()后,使用app.set設(shè)置express內(nèi)部的一些參數(shù)(options)使用app.use來(lái)注冊(cè)函數(shù),可以簡(jiǎn)單的認(rèn)為是向那個(gè)tasks的數(shù)組進(jìn)行push操作
3.通過(guò)
http.createServer用app來(lái)處理請(qǐng)求
試驗(yàn)1. 向express中注冊(cè)自定義函數(shù)
注冊(cè)進(jìn)express中的函數(shù):
- 長(zhǎng)成下面這個(gè)樣子
function(req, res, next){
//...我們自己的邏輯
next();
}
或者:
app.use(function(err,req,res,next){
if(err){
//自己的處理錯(cuò)誤的邏輯
console.log(err.message);
console.log(err.stack);
res.end('404')
}
});
app.use(customerFunc)要寫(xiě)在下面兩句的前面
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
一般情況下是這麼使用的,但是對(duì)於通用error處理可以放到最後,http.createServer之前
app.use(express.static(path.join(__dirname, '/web/dist')));
// catch 404 and forwarding to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// 啟動(dòng)及端口
http.createServer(app).listen(app.get('port'), function(req, res){
console.log('啟動(dòng)成功,端口為' + app.get('port'));
console.log('主頁(yè)地址:http://localhost:' + app.get('port'));
});
關(guān)于第2點(diǎn),是因?yàn)槁酚珊蠡蛘?qǐng)求靜態(tài)資源后,一次請(qǐng)求響應(yīng)的生命周期實(shí)質(zhì)上已經(jīng)結(jié)束,加在這后面進(jìn)行請(qǐng)求處理,沒(méi)有任何意義。
關(guān)于第1點(diǎn),寫(xiě)點(diǎn)代碼繼續(xù)進(jìn)行試驗(yàn):
app.use(function(req,res,next){
console.log("111");
next();
});
如果不寫(xiě)next(),那么后面注冊(cè)的函數(shù)就不會(huì)執(zhí)行(一個(gè)大臉疑問(wèn)),運(yùn)行測(cè)試下不就知道了
app.use(function(req,res,next){
console.log('111');
next();
console.log('222');
});
app.use(function(req,res,next){
console.log("333");
next();
});
那么控制臺(tái)的輸出的順序是:111 333 222
試驗(yàn)二 next()的工作原理
整個(gè)處理請(qǐng)求的模型還是比較簡(jiǎn)單的,在理解的上面的過(guò)程后,能不能不借助express,自己實(shí)現(xiàn)上面的過(guò)程呢,主要是怎么處理next()那一塊
var http = require('http');
function express(){
var funcs = [];
var expr = function(req,res){
var i = 0;
function next(){
var task = funcs[i++];
if(!task) return;
task(req,res,next);
}
next();
}
expr.use=function(f){
funcs.push(f);
}
return expr;
}
var app = express();
app.use(function(req,res,next){
console.log('haha');
next();
});
app.use(function(req,res,next){
console.log('hehe');
next();
});
app.use(function(req,res){
res.end("there is nothing happened");
});
http.createServer(app).listen('3000', function(){
console.log('Express server listening on port 3000');
});
啟動(dòng)服務(wù)后,每來(lái)一個(gè)請(qǐng)求,控制臺(tái)會(huì)依次輸出haha hehe,然后瀏覽器是there is nothing happened
當(dāng)然如果要更深一步了解到,可以去看源代碼,實(shí)際上這一部分的主要代碼是在connect中的,在connect/lib/proto.js 這個(gè)源文件中,主要是app.use,和app.handle 兩個(gè)函數(shù)中
後續(xù)會(huì)再次研究express的app.use原理。