在 javascript 裡,如果我們想用一個函數處理數組 (Array) 中的每個元素,那我們有很多種選擇,最簡單的當然就是用自帶的 forEach 函數(低版本也可以使用 lodash 中的 forEach 函數):
const arr = [0,1,2,3,4,5,6,7,8,9];
arr.forEach(item=>{ console.log(item) });//依次輸出
除了這種周遊,數組還有一種很常用的操作,就是拿來遞歸,js中的數組自帶了 pop 和 push 方法,其實也可以當作一個連結清單來用,配合遞歸自然也是相當好用:
const arr = [0,1,2,3,4,5,6,7,8,9];
const func = (arr)=>{
item = arr.pop();
console.log(item);
if (arr.length==0) return;
return func(arr);
}
func(arr)
這樣也能實作和之前 forEach 類似的效果~
既然效果差不多,那我們為啥要搞出這麼麻煩的東西??
嘛……有些場景下周遊操作也不是那麼好用的啦……比如我以前博文中寫到的那個爬蟲
"use strict"
const request = require('request')
const fs = require('fs')
const arr = [
'http://path.to/img1.jpg',
'http://path.to/img2.jpg',
'http://path.to/img3.jpg',
...
'http://path.to/img498.jpg',
'http://path.to/img499.jpg',
'http://path.to/img500.jpg'
]
arr.forEach(src=> {
//下載下傳圖檔
const ws = fs.createWriteStream('./download/'+src.split('/').pop());
ws.on('close', ()=>{
console.log('下載下傳完成')
})
request(src).pipe(ws);
})
因為 request 是一個異步操作,是以它并不會阻塞 forEach ,也就是說上面這個 demo 一旦運作起來,就會建立500個并發的網絡請求和檔案寫入……
這就不太好玩了,一般來說并發請求可以提高網絡利用效率,但效率再怎麼提高,帶寬也是有限的,并發過多就會導緻單個請求變慢,請求過慢就可能會被服務端幹掉,被服務端幹掉的話我們就拿不到想要的圖檔了

是以我們期望的效果是依次執行下載下傳操作,下載下傳完一個再去下載下傳另一個,這時候就比較适合用遞歸了~
"use strict"
const request = require('request')
const fs = require('fs')
const arr = [
'http://path.to/img1.jpg',
'http://path.to/img2.jpg',
'http://path.to/img3.jpg',
...
'http://path.to/img498.jpg',
'http://path.to/img499.jpg',
'http://path.to/img500.jpg'
]
const download = (arr)=>{
src = arr.pop();
//下載下傳圖檔
const ws = fs.createWriteStream('./download/'+src.split('/').pop());
ws.on('close', ()=>{
console.log('下載下傳完成')
if (arr.length>0)
return download(arr);
})
request(src).pipe(ws);
}
download (arr);
這樣我們就可以依次下載下傳圖檔啦~
可是既然是經常會用的東西……有沒有更友善的用法啊,就像forEach那樣的……?
那把遞歸這個操作抽象出來,直接定義到數組的原型 (prototype) 上吧
Array.prototype.recursion = function (func) {
const re = function (arr) {
if (!arr.length) return;
const item = arr.pop();
return func(item, function () {
return re(arr);
});
}
re(this);
}
于是乎這樣随便寫了一下,雖然很簡陋,但好歹是可以用的, 使用方式如下:
"use strict"
const request = require('request')
const fs = require('fs')
const arr = [
'http://path.to/img1.jpg',
'http://path.to/img2.jpg',
'http://path.to/img3.jpg',
...
'http://path.to/img498.jpg',
'http://path.to/img499.jpg',
'http://path.to/img500.jpg'
]
arr.recursion((src, next)=> {
//下載下傳圖檔
const ws = fs.createWriteStream('./download/'+src.split('/').pop());
ws.on('close', ()=>{
console.log('下載下傳完成');
//目前異步操作完成後,調用next來進行下一次操作
next()
})
request(src).pipe(ws);
})
其實我也不知道這樣做是否合适,連結清單加遞歸這種做法畢竟還是更适合函數式風格,而操作原型這種做法更多的則是面向對象風格,函數式程式設計比較強調無副作用,而顯然……我現在這種做法是有副作用的,遞歸過程中不斷pop(),遞歸完成後,arr 就變成一個空數組了。
其實這也還隻是一個半成品,我們可能還希望在遞歸完成的時候,再繼續執行一些操作,比如說把下載下傳下來的圖檔打個壓縮包?或者發一條消息告訴我檔案都下載下傳完成了之類的。
不管怎麼說,這也是一個思路,如果發現這個思路中有其他嚴重的問題,或者有更好的建議,也歡迎指教~
轉載于:https://www.cnblogs.com/Eden-cola/p/recursion-with-prototype.html