失落迷茫了好一段日子。終于我用接觸2個月的技術Nodejs成功的混到一份工作。嚴格來說隻學習了3天(白天睡覺,晚上通宵學習),後面的時間都是在配置環境。總的來說,函數式程式設計是有應用的市場的,而且學習門檻也不是太高。就算從來沒聽說過函數式程式設計的人也會知道javascript,也會使用jquery。雖然很多是把它當作過程式的來用,來看待。這也是在于它的文法看起來太像C,太像過程式的語言。
之前一直想寫一些關于函數程式設計文章來記錄我學習的曆程。之前寫了一篇使用F#的,不過大家好像對F#比較排斥。以後我從工作出發寫nodejs的吧。
好了。廢話不多說我們先從一個具體的項目來分析函數式程式設計吧。
用webstorm建立一個express項目,這是nodejs下用來做web伺服器的庫。會生成類似下面這個結構的檔案。
- /bin/www : 項目的啟動檔案,配置了監聽的端口,當然程式入口還是app.js
- /node_modules/ : 通過npm包管理中間件都在這,包括session,模闆,日志等中間件,你自己安裝的中間件也在這
- /public/ : 暴露的檔案夾,從名字就可以看出,圖面前端js腳本和css會在這裡
- /routes/ : 路由,相當于控制器
- /views/ : 模闆檔案
- /app.js : 約定俗成的項目入口
- /package.json : 配置你項目依賴的包,使用npm指令 npm install -d 會自動安裝裡面記錄的中間件,非常友善。由于nodejs的中間件不完全是腳本組成的,也會包含C寫的編譯檔案,各環境下不盡相同,是以通過npm,本地下載下傳編譯是非常重要的
總的來說檔案結構隻是約定俗成,或是按人們習慣來用的。不像java、C之類的會有main函數作為入口。任何檔案都能當作啟動入口。nodejs也不僅限于開發web伺服器,加上各種奇葩的中間件的運用,會讓項目變成各種形态。這是一個自由度非常高的開發平台。
我們先寫一個簡單的demo。由于js的文法太過糾結,我們使用另外一種語言coffeescript,他是一個nodejs的庫。能自己運作在nodejs上,也能編譯成js檔案。這裡我們隻是用做文法糖,仍然編譯成js檔案。我會貼出兩種代碼來适應不同的需要。
coffeescript
fna = ->
console.log("I am 'a'")
fnb = ->
console.log "I am 'b'"
fna()
fnb()
javscript
// Generated by CoffeeScript 1.7.1
(function() {
var fna, fnb;
fna = function() {
return console.log("I am 'a'");
};
fnb = function() {
return console.log("I am 'b'");
};
fna();
fnb();
}).call(this);
//# sourceMappingURL=test.map
這裡我編寫了兩個函數,并依次調用它們。coffeescript會嚴格申明變量和閉包,不會讓其污染全局變量。代碼精簡不少,看起來也更像是函數式程式設計了。輸入結果顯而易見。
console.log
i am 'a'
i am 'b'
nodejs是異步執行的。如果這是兩個有關聯的函數呢?
coffeescript
fna = ->
console.log("這是母雞")
fnb = ->
console.log "母雞下蛋"
fna()
fnb()
javascript
// Generated by CoffeeScript 1.7.1
(function() {
var fna, fnb;
fna = function() {
return console.log("這是母雞");
};
fnb = function() {
return console.log("母雞下蛋");
};
fna();
fnb();
}).call(this);
//# sourceMappingURL=test.map
單從結果來看,好像沒有什麼問題。
console.log
這是母雞
母雞下蛋
在實際項目中,我們并不知道兩個函數内部到底幹了什麼,就像蝴蝶效應,任何改動都可能讓結果發生變動。
coffeescript
fna = ->
setTimeout ->
console.log("這是母雞")
, 100
fnb = ->
console.log "母雞下蛋"
fna()
fnb()
javascript
// Generated by CoffeeScript 1.7.1
(function() {
var fna, fnb;
fna = function() {
return setTimeout(function() {
return console.log("這是母雞");
}, 100);
};
fnb = function() {
return console.log("母雞下蛋");
};
fna();
fnb();
}).call(this);
//# sourceMappingURL=test.map
console.log
母雞下蛋
這是母雞
現在就不是我們想要的結果了。其實這種異步方式也很好了解,它隻管函數調用,而不管函數結果。在同步程式設計中,前一步操作會阻塞後一步操作,母雞下蛋的操作會等着這隻母雞出結果。而異步程式設計中,不會阻塞後面的任務進行,就像指揮官給手下發派任務,手下都會去執行各自的任務,但什麼時候完成任務就不好說了。這樣做的好處就是在執行耗時任務的時候,其他的任務也能繼續執行,或者同時執行多個耗時任務。但是有利有弊,在流程控制上會比較糾結。正常做法是用回調函數,就像有人說過,世上本來沒有回調,用的人多了也就有了回調函數。
coffeescript
fna = (next) ->
setTimeout ->
console.log("這是母雞")
next()
, 1000
fnb = ->
console.log "母雞下蛋"
fna ->
fnb()
javascript
// Generated by CoffeeScript 1.7.1
(function() {
var fna, fnb;
fna = function(next) {
return setTimeout(function() {
console.log("這是母雞");
return next();
}, 1000);
};
fnb = function() {
return console.log("母雞下蛋");
};
fna(function() {
return fnb();
});
}).call(this);
//# sourceMappingURL=test.map
conslole.log
這是母雞
母雞下蛋
這中方法雖然解決了關聯函數的流程控制問題,但是也有新的問題。邏輯複雜的時候,回調嵌套就會越來越深。
coffeescript
fna = (next) ->
setTimeout ->
console.log("這是母雞")
next()
, 1000
fnb = (next) ->
setTimeout ->
console.log "母雞下蛋"
next()
, 100
fnc = ->
console.log "蛋孵出了雞"
fna ->
fnb ->
fnc()
javascript
// Generated by CoffeeScript 1.7.1
(function() {
var fna, fnb, fnc;
fna = function(next) {
return setTimeout(function() {
console.log("這是母雞");
return next();
}, 1000);
};
fnb = function(next) {
return setTimeout(function() {
console.log("母雞下蛋");
return next();
}, 100);
};
fnc = function() {
return console.log("蛋孵出了雞");
};
fna(function() {
return fnb(function() {
return fnc();
});
});
}).call(this);
//# sourceMappingURL=test.map
console.log
這是母雞
母雞下蛋
蛋孵出了雞
幸好有中間件解決這個問題。async 中間件有各種流程控制方法。其中series就能很優美的實作這個邏輯。你所要做的就是每個函數裡加上一個回調next執行下一步操作,第一個參數是err,第二個參數能追加一個結果,在async最後的回調中傳回出來。
coffeescript
async = require "async"
fna = (next) ->
setTimeout ->
console.log "這是母雞"
next(null, 1)
, 1000
fnb = (next) ->
setTimeout ->
console.log "母雞下蛋"
next(null, 2)
, 2000
fnc = (next) ->
setTimeout ->
console.log "蛋孵出了雞"
next(null, 3)
, 100
async.series [
fna
fnb
fnc
]
, (err, results) ->
console.log results
javascript
// Generated by CoffeeScript 1.7.1
(function() {
var async, fna, fnb, fnc;
async = require("async");
fna = function(next) {
return setTimeout(function() {
console.log("這是母雞");
return next(null, 1);
}, 1000);
};
fnb = function(next) {
return setTimeout(function() {
console.log("母雞下蛋");
return next(null, 2);
}, 2000);
};
fnc = function(next) {
return setTimeout(function() {
console.log("蛋孵出了雞");
return next(null, 3);
}, 100);
};
async.series([fna, fnb, fnc], function(err, results) {
return console.log(results);
});
}).call(this);
//# sourceMappingURL=test.map
console.log
這是母雞
母雞下蛋
蛋孵出了雞
[ 1, 2, 3 ]
更好的封裝,應該是這個樣子。
coffeescript
async = require "async"
fna = (next) ->
setTimeout ->
console.log "這是母雞"
next()
, 1000
fnb = (next) ->
setTimeout ->
console.log "母雞下蛋"
next()
, 2000
fnc = (next) ->
setTimeout ->
console.log "蛋孵出了雞"
next()
, 100
async.series [
(next) ->
fna ->
next null, 1
(next) ->
fnb ->
next null, 2
(next) ->
fnc ->
next null, 3
]
, (err, results) ->
console.log results
javascript
// Generated by CoffeeScript 1.7.1
(function() {
var async, fna, fnb, fnc;
async = require("async");
fna = function(next) {
return setTimeout(function() {
console.log("這是母雞");
return next();
}, 1000);
};
fnb = function(next) {
return setTimeout(function() {
console.log("母雞下蛋");
return next();
}, 2000);
};
fnc = function(next) {
return setTimeout(function() {
console.log("蛋孵出了雞");
return next();
}, 100);
};
async.series([
function(next) {
return fna(function() {
return next(null, 1);
});
}, function(next) {
return fnb(function() {
return next(null, 2);
});
}, function(next) {
return fnc(function() {
return next(null, 3);
});
}
], function(err, results) {
return console.log(results);
});
}).call(this);
//# sourceMappingURL=test.map
到這裡coffeescript還可以一戰,js你已經完全看不懂了對不對?
今天就寫到這裡了,我接觸到的範圍也不廣,以後大家有什麼關于函數式程式設計的問題可以告知,大家一起解決。