第三回。
any.js
var any = _.some = _.any = function(obj, predicate, context) {
predicate || (predicate = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context);
each(obj, function(value, index, list) {
if (result || (result = predicate.call(context, value, index, list))) return breaker;
});
return !!result;
};
ネイティブではsome関数になります。基本的にはeachで順番にテストしていき、predicateがtrueになる要素が見つかったらbreakします。breakはbreakerを返すことで実現しています。
breakerはunderscore内でのみ参照可能な変数で空のオブジェクトが代入されています。したがって、eachに渡す関数が空のオブジェクトを返したとしてもbreakは起こらず、underscore内の関数のみが安全にbreakできます。
最後の「!!」はresultにboolean以外のものが代入された場合に、booleanを返せるようにするイディオムです(たぶん、、、)。
find.js
_.find = _.detect = function(obj, predicate, context) {
var result;
any(obj, function(value, index, list) {
if (predicate.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
anyで順番にテストしていって見つかったら早期終了して返すだけですね。見つからなかったらundefinedにするのは仕様のようです。
filter.js
_.filter = _.select = function(obj, predicate, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context);
each(obj, function(value, index, list) {
if (predicate.call(context, value, index, list)) results.push(value);
});
return results;
};
これもeachで回してテストをパスするやつをpushしていくだけです。ちなみにリーダブルコードではfilterという名前は曖昧だから使うなとありました。まぁネイティブの実装がfilterという名前だから合わせたのでしょうけど。
reject.js
_.reject = function(obj, predicate, context) {
return _.filter(obj, function(value, index, list) {
return !predicate.call(context, value, index, list);
}, context);
};
filterを使うとrejectは論理を反転させるだけで作れます。