2014-11-20 53 views
2

這是一個回調(稍微修改/簡化爲這個問題的目的)我已經寫了處理一些對象數據。展平嵌套回調

function (err, data) { 
    var rv; 
    if (data && data.result instanceof Array) { 
     rv = data.result.map(function (value) { 
     if (value && value.track_contributions instanceof Array) { 
      return value.track_contributions.map(function (innerValue) { 
      if (innerValue && innerValue.track) { 
       return innerValue.track.mid; 
      } 
      }); 
     } 
     }); 
     // flatten nested arrays 
     rv = [].concat.apply([], rv); 
    } 
    console.log(rv); 
    }; 

這裏是說我處理看起來像對象:

{ 
    "result": [ 
    { 
     "type": "/music/artist", 
     "track_contributions": [ 
     { 
      "track": { 
      "mid": "/m/015rm3l" 
      } 
     }, 
     { 
      "track": { 
      "mid": "/m/0nm2km" 
      } 
     }, 
     { 
      "track": { 
      "mid": "/m/010ksbq" 
      } 
     }, 
     ... 
     ] 
    } 
    ] 
} 

而且我想從我的回調返回值,看起來像這樣:

[ '/m/015rm3l', 
    '/m/0nm2km', 
    '/m/010ksbq', 
    ... 
] 

我的代碼工作很好,但我覺得所有的嵌套都是代碼味道。

我應該如何讓這種類型的代碼更平坦,更具可讀性和可維護性,以及所有優秀的東西?承諾?一些lodash類型的工具?還有別的嗎?上述所有的?

+0

如果'map'函數中的條件不成立,這種方法會導致很多'undefined'值。因爲在這些情況下,沒有任何東西會返回。 – Amberlamps 2014-11-20 22:51:56

+0

@Amberlamps這是正確的。我的代碼期望這一點。但是,值得注意! – Trott 2014-11-20 22:56:21

回答

1

這裏的,我認爲是更容易理解和不重複的代碼維護,因爲它採用的是通用的遍歷一個回調添加你的數據結構的具體版本:

function process(err, data) { 
    var rv = []; 
    function doObject(obj, key, callback) { 
     if (obj[key] && obj[key] instanceof Array) { 
      obj[key].forEach(function(value) { 
       callback(value); 
      }); 
     } 
    } 
    doObject(data, "result", function(result) { 
     doObject(result, "track_contributions", function(item) { 
      if (item.track && item.track.mid) { 
       rv.push(item.track.mid); 
      } 
     }); 

    }); 
    console.log(rv); 
} 

不改變您正在使用的基本算法,您可以初始化rv並直接將結果直接推送到rv,而不是製作必須變平的子陣列。

function process(err, data) { 
    var rv = []; 
    if (data && data.result instanceof Array) { 
     data.result.forEach(function(value) { 
      if (value && value.track_contributions instanceof Array) { 
       value.track_contributions.forEach(function(innerValue) { 
        if (innerValue && innerValue.track) { 
         rv.push(innerValue.track.mid); 
        } 
       }); 
      } 
     }); 
    } 
    console.log(rv); 
} 

因爲你正在尋找在每個級別的屬性名稱不同,它不是有效,或者易於使用遞歸來避免重複的代碼(儘管這是一個選項)。

這是一個兩級遞歸算法,但我不認爲增加的複雜性實際上是值得的。如果你要超過2層深或水平的任意數量的它可能會更有意義:

function process(err, data) { 
    var rv = []; 
    function doArray(item, key1, key2) { 
     if (item && item[key1] instanceofArray) { 
      item[key1].forEach(function(value) { 
       if (key2) { 
        doArray(value, key2); 
       } else if (value && value.track.mid) { 
        rv.push(value.track.mid); 
       } 
      }); 
     } 
    } 
    doArray(data, "result", "track_contributions"); 
    console.log(rv); 
} 
+0

添加了遞歸算法。 – jfriend00 2014-11-20 22:59:00

+0

這是一個有趣的解決方案,但它既不可讀也不可維護。此外,遞歸在這裏完全沒有必要。 – Amberlamps 2014-11-20 23:09:25

+0

@Amberlamps - 在我的答案中查看關於遞歸解決方案的註釋。國際海事組織,我的第一個解決方案比OP更容易理解(直接將結果推入結果數組而不是生成嵌套數組),並且略短。沒有什麼大的收穫,但是這個代碼沒有太多的工作要做,所以這就是我所能找到的。 – jfriend00 2014-11-20 23:15:38

1

這種做法會得到你想要的輸出:

function getArray(err, data) { 

    var rv; 

    function mapArray(obj, array, callback) { 
    if (obj && obj[array] instanceof Array) { 
     return obj[array].map(callback); 
    } 
    } 

    function getValue(value) { 
    return mapArray(value, 'track_contributions', getInnerValue); 
    } 

    function getInnerValue(innerValue) { 
    return innerValue.track && innerValue.track.mid; 
    } 

    rv = [].concat.apply([], mapArray(data, 'result', getValue)); 

    console.log(rv); 

}; 

getArray(null, data); 

,關鍵是要拆的每功能到其預期的目的,並重用每一個重複的功能。

該實例的映射完成兩次。所以讓我們寫一個通用函數mapArray。它期望obj,數組屬性array的名稱和回調。

對於您示例中的外部函數,我們有mapArray(data, 'result', getValue),對於內部函數,它是mapArray(value, 'track_contributions', getInnerValue)

getValue函數裏面我們定義了外部函數,而getInnerValues定義了內部函數。

完成。

0

您正在將數據結構的遍歷與要應用於其節點的域邏輯進行混合,因此存在代碼異味。最好解耦兩個方面,使用帶有域函數的通用遍歷算法(在當前情況下爲收集器)。

例如:

function traverse(source, collector) { 
    for(var property in source) { 
     var value = source[property]; 
     if (value instanceof Array) { 
     for(var ii=0; ii<value.length; ii++) { 
      arguments.callee(value[ii], collector); 
     } 
     } else if (value instanceof Object){ 
      arguments.callee(value, collector); 
     } else { 
      collector(property, value); 
     } 
    } 
    } 

    var values=[]; 
    traverse(source, function(property, value) { 
    if (property == "mid") { 
     values.push(value); 
    } 
    }); 
    document.write(values.join(", ")); 

遍歷算法可能會更復雜,這取決於你的約束原始數據結構的規則;確保最終保持最小(即不要編寫不會使用的代碼)。

+0

這不就是收集所有'mid'屬性,不管它們在數據結構中的什麼位置?而且,這是否也會迭代數組上的任何枚舉屬性?而且,這也不會在「嚴格」模式下失敗嗎? – jfriend00 2014-11-20 23:52:56

+0

1)是的,他們會收集所有「中等」屬性,因爲我的印象是這是OP的目標,並希望儘量減少代碼。人們可以很容易地改變遍歷算法,例如,如果這是一個需求,它可以收集並傳遞完整的屬性路徑(而不僅僅是最後一位)。 – Elegie 2014-11-21 07:39:53

+0

至於你的其他評論:「for in」將遍歷枚舉屬性,這是我們在這裏想要的。該代碼確實與「嚴格模式」不兼容,因爲它使用了arguments.callee。在這兩種情況下,如果您的要求不同,請不要猶豫,調整代碼以符合要求!要除去arguments.callee,你可以直接引用與函數聲明相關的命名屬性(因爲它訪問一個外部的自由變量名,所以乾淨一點),或者把函數改成一個命名的函數表達式(使用普通的瀏覽器警告)。 – Elegie 2014-11-21 07:56:24