用遞歸的方式處理數組 && 把遞歸方法方法定義到數組的原型上 (這是一次腦洞大開的神奇嘗試)


在 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 就變成一個空數組了。
其實這也還只是一個半成品,我們可能還希望在遞歸完成的時候,再繼續執行一些操作,比如說把下載下來的圖片打個壓縮包?或者發一條消息告訴我文件都下載完成了之類的。
不管怎么說,這也是一個思路,如果發現這個思路中有其他嚴重的問題,或者有更好的建議,也歡迎指教~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM