在 javascript 里,如果我們想用一個(gè)函數(shù)處理數(shù)組 (Array) 中的每個(gè)元素,那我們有很多種選擇,最簡(jiǎn)單的當(dāng)然就是用自帶的 forEach 函數(shù)(低版本也可以使用 lodash 中的 forEach 函數(shù)):

const arr = [0,1,2,3,4,5,6,7,8,9];arr.forEach(item=>{ console.log(item) });//依次輸出

除了這種遍歷,數(shù)組還有一種很常用的操作,就是拿來(lái)遞歸,js中的數(shù)組自帶了 pop 和 push 方法,其實(shí)也可以當(dāng)作一個(gè)鏈表來(lái)用,配合遞歸自然也是相當(dāng)好用:

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)

這樣也能實(shí)現(xiàn)和之前 forEach 類(lèi)似的效果~
既然效果差不多,那我們?yōu)樯兑愠鲞@么麻煩的東西??
嘛……有些場(chǎng)景下遍歷操作也不是那么好用的啦……比如我以前博文中寫(xiě)到的那個(gè)爬蟲(chóng)

"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);})

因?yàn)?request 是一個(gè)異步操作,所以它并不會(huì)阻塞 forEach ,也就是說(shuō)上面這個(gè) demo 一旦運(yùn)行起來(lái),就會(huì)創(chuàng)建500個(gè)并發(fā)的網(wǎng)絡(luò)請(qǐng)求和文件寫(xiě)入……
這就不太好玩了,一般來(lái)說(shuō)并發(fā)請(qǐng)求可以提高網(wǎng)絡(luò)利用效率,但效率再怎么提高,帶寬也是有限的,并發(fā)過(guò)多就會(huì)導(dǎo)致單個(gè)請(qǐng)求變慢,請(qǐng)求過(guò)慢就可能會(huì)被服務(wù)端干掉,被服務(wù)端干掉的話我們就拿不到想要的圖片了
Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)
所以我們期望的效果是依次執(zhí)行下載操作,下載完一個(gè)再去下載另一個(gè),這時(shí)候就比較適合用遞歸了~

"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);

這樣我們就可以依次下載圖片啦~
可是既然是經(jīng)常會(huì)用的東西……有沒(méi)有更方便的用法啊,就像forEach那樣的……?
那把遞歸這個(gè)操作抽象出來(lái),直接定義到數(shù)組的原型 (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);}

于是乎這樣隨便寫(xiě)了一下,雖然很簡(jiǎn)陋,但好歹是可以用的, 使用方式如下:

"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('下載完成');
        //當(dāng)前異步操作完成后,調(diào)用next來(lái)進(jìn)行下一次操作
        next()    })    request(src).pipe(ws);})

其實(shí)我也不知道這樣做是否合適,鏈表加遞歸這種做法畢竟還是更適合函數(shù)式風(fēng)格,而操作原型這種做法更多的則是面向?qū)ο?/strong>風(fēng)格,函數(shù)式編程比較強(qiáng)調(diào)無(wú)副作用,而顯然……我現(xiàn)在這種做法是有副作用的,遞歸過(guò)程中不斷pop(),遞歸完成后,arr 就變成一個(gè)空數(shù)組了。
其實(shí)這也還只是一個(gè)半成品,我們可能還希望在遞歸完成的時(shí)候,再繼續(xù)執(zhí)行一些操作,比如說(shuō)把下載下來(lái)的圖片打個(gè)壓縮包?或者發(fā)一條消息告訴我文件都下載完成了之類(lèi)的。
不管怎么說(shuō),這也是一個(gè)思路,如果發(fā)現(xiàn)這個(gè)思路中有其他嚴(yán)重的問(wèn)題,或者有更好的建議,也歡迎指教~