2014-06-19 40 views
5

我正在努力將許多標準Underscore.js函數的底層代碼重寫爲工作在我的JavaScript技能上,有點卡住了_.every/_.all。看起來在庫本身中,_.every/_.all函數僅使用現有的_.each函數編寫,但我被鼓勵使用我的版本_.reduce(它已經合併我的版本_.each)編寫一個版本。我在下面提供了這兩個函數的代碼。如何使用_.reduce(和_.each)重寫_ _very/_。all來自Underscore.js

第一個測試我_.every功能(見下文也一樣)失敗是一個地方全是假的值在使用_.identity函數傳遞(簡單地返回輸入爲參數值)作爲迭代器:

測試:

it('fails for a collection of all-falsy results', function() { 
    expect(_.every([null, 0, undefined], _.identity)).to.equal(false); 
    }); 

我有幾個問題,爲什麼我的_.every功能失敗上面的測試,與多個其他測試一起(例如,混合真/假值,不確定值等):

- 調用迭代器函數時,是否需要使用iterator.calliterator.apply?如果是這樣,我該怎麼使用以及如何指定參數?

- 在這裏使用_.reduce而不僅僅是_.each有什麼好處,特別是當Underscore.js庫不使用_.reduce

- 爲什麼不回需要被調用兩次,一次調用_.reduce功能時,和內內_.reduce定義的匿名函數一次(我還構建利用_.map功能函數時不知道這一點)?對我來說,我似乎返回了_.reduce函數的結果,該函數已經返回了一些東西。

_.every:

_.every = function(collection, iterator) { 
    // TIP: Try re-using reduce() here. 
    return _.reduce(collection, function(allFound, item) { 
     return iterator(item) && allFound; 
    }, true); 
    }; 

_.each:

_.each = function(collection, iterator) { 
    // define spec for arrays 
    if (Array.isArray(collection)) { 
    for(var i = 0; i < collection.length; i++) { 
     iterator(collection[i], i, collection); 
    } 
    } 

    // define spec for objects 
    else { 
    for(var key in collection) { 
     iterator(collection[key], key, collection); 
    } 
    } 
}; 

_.reduce:

_.reduce = function(collection, iterator, accumulator) { 

    // add condition to set accumulator if no explicit starting value is given. 
    if (arguments.length < 3) { 
     accumulator = collection[0]; 
    } 

    _.each(collection, function(value) { 
     accumulator = iterator(accumulator, value); 
    }); 

    return accumulator; 
    }; 

回答

4

您的測試沒有通過,因爲它沒有返回false預期(儘管它返回了一個錯誤的值)。

_.every = function(collection, iterator) { 
    return _.reduce(collection, function(allFound, item) { 
     return iterator(item) && allFound; 
    }, true); 
}; 

當您返回iterator(item) && allFound會發生什麼情況是,如果iterator(item)是falsey(但不是false),它不會返回false,但iterator(item)值。要自己驗證,請打開REPL,然後鍵入undefined && true;結果將是undefined,而不是false

所以,如果你想明確地返回false,而不僅僅是一個falsey值,你必須強制它爲布爾值。你可以做Boolean(truthy_or_falsey_value)!!truthy_or_falsey_value。我通常更喜歡後者,所以正是如此改變的實現:

_.every = function(collection, iterator) { 
    return _.reduce(collection, function(allFound, item) { 
     return !!iterator(item) && allFound; 
    }, true); 
}; 

你的其他問題:

當調用迭代器功能,做我需要使用iterator.call或 iterator.apply?如果是這樣,我該怎麼使用以及如何指定參數?

這取決於你的目標是什麼。當您想要控制函數體中關鍵字this的值時,主要使用callapply。一些JavaScript的內置數組方法(如Array.prototype.mapArray.prototype.filter)採用thisArg,這是使用callapply提供的回調函數。至於callapply之間的差異,它只是如何處理參數。有關更多詳細信息,請參見this answer

什麼好處是有在這裏使用reduce,而不僅僅是each, 尤其是當Underscore.js庫不使用reduce

大概沒有或很少。可能會有性能差異,但找出最好的方法是分析兩種方法。

爲什麼需要回歸到被稱爲兩次,一次調用 _.reduce功能時,一旦內如果你想要一個功能_.reduce

中定義的匿名函數 - 任何函數 - 要返回一個值,您必須在該函數內調用返回值。你不能指望從內部函數調用return,並期望封閉函數神奇地理解它應該返回被調用函數的值。根據您的觀點,如果return沒有明確調用,某些語言默認返回函數中最後一個表達式的值,這可能很方便,也可能會造成混淆。如果你有這種語言的經驗(例如Ruby),那麼所有return陳述對你來說似乎有點過分。

作爲編輯說明,我覺得iterator是測試功能的一個糟糕的命名選擇。它實際上並沒有迭代任何東西(它是進行任何迭代的參數)。更好的名字可能是非常通用的callbackcb。術語"predicate"表示將值映射到truefalse的函數,這是我的首選術語。另一個常見的選擇就是test,因爲它畢竟只是一個對其參數執行二進制過濾器的函數。