天天看點

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

在 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

繼續閱讀