Improve compliance with Array methods in ES5 when passed a null value. (ES5 Array methods treat null like [])

This commit is contained in:
Ryan W Tenney
2010-12-06 23:30:43 -05:00
parent e63f44f798
commit cdcf18b79c
3 changed files with 47 additions and 5 deletions

View File

@@ -83,6 +83,7 @@ $(document).ready(function() {
equals(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); equals(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function');
var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3); var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3);
equals(result, 1, 'works on an arguments object'); equals(result, 1, 'works on an arguments object');
equals(_.indexOf(null, 2), -1, 'handles nulls properly');
}); });
test("arrays: lastIndexOf", function() { test("arrays: lastIndexOf", function() {
@@ -92,6 +93,7 @@ $(document).ready(function() {
equals(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); equals(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element');
var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0); var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0);
equals(result, 5, 'works on an arguments object'); equals(result, 5, 'works on an arguments object');
equals(_.indexOf(null, 2), -1, 'handles nulls properly');
}); });
test("arrays: range", function() { test("arrays: range", function() {

View File

@@ -29,6 +29,10 @@ $(document).ready(function() {
answers = []; answers = [];
_.each({range : 1, speed : 2, length : 3}, function(v){ answers.push(v); }); _.each({range : 1, speed : 2, length : 3}, function(v){ answers.push(v); });
ok(answers.join(', '), '1, 2, 3', 'can iterate over objects with numeric length properties'); ok(answers.join(', '), '1, 2, 3', 'can iterate over objects with numeric length properties');
answers = 0;
_.each(null, function(){ ++answers; });
equals(answers, 0, 'handles a null properly');
}); });
test('collections: map', function() { test('collections: map', function() {
@@ -46,6 +50,9 @@ $(document).ready(function() {
var ids = _.map(document.images, function(n){ return n.id; }); var ids = _.map(document.images, function(n){ return n.id; });
ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections'); ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections');
var ifnull = _.map(null, function(){});
ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
}); });
test('collections: reduce', function() { test('collections: reduce', function() {
@@ -64,6 +71,16 @@ $(document).ready(function() {
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }); var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; });
equals(sum, 6, 'default initial value'); equals(sum, 6, 'default initial value');
var ifnull;
try {
_.reduce(null, function(){});
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
}); });
test('collections: reduceRight', function() { test('collections: reduceRight', function() {
@@ -75,6 +92,16 @@ $(document).ready(function() {
var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }); var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; });
equals(list, 'bazbarfoo', 'default initial value'); equals(list, 'bazbarfoo', 'default initial value');
var ifnull;
try {
_.reduceRight(null, function(){});
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
}); });
test('collections: detect', function() { test('collections: detect', function() {

View File

@@ -68,6 +68,7 @@
// Delegates to **ECMAScript 5**'s native `forEach` if available. // Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) { var each = _.each = _.forEach = function(obj, iterator, context) {
var value; var value;
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) { if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context); obj.forEach(iterator, context);
} else if (_.isNumber(obj.length)) { } else if (_.isNumber(obj.length)) {
@@ -86,8 +87,9 @@
// Return the results of applying the iterator to each element. // Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available. // Delegates to **ECMAScript 5**'s native `map` if available.
_.map = function(obj, iterator, context) { _.map = function(obj, iterator, context) {
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
var results = []; var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list); results[results.length] = iterator.call(context, value, index, list);
}); });
@@ -98,6 +100,7 @@
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = memo !== void 0; var initial = memo !== void 0;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) { if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context); if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
@@ -105,16 +108,19 @@
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
if (!initial && index === 0) { if (!initial && index === 0) {
memo = value; memo = value;
initial = true;
} else { } else {
memo = iterator.call(context, memo, value, index, list); memo = iterator.call(context, memo, value, index, list);
} }
}); });
if (!initial) throw new TypeError("Reduce of empty array with no initial value");
return memo; return memo;
}; };
// The right-associative version of reduce, also known as `foldr`. // The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available. // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) { _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context); if (context) iterator = _.bind(iterator, context);
return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
@@ -139,8 +145,9 @@
// Delegates to **ECMAScript 5**'s native `filter` if available. // Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`. // Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) { _.filter = _.select = function(obj, iterator, context) {
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
var results = []; var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value; if (iterator.call(context, value, index, list)) results[results.length] = value;
}); });
@@ -150,6 +157,7 @@
// Return all the elements for which a truth test fails. // Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) { _.reject = function(obj, iterator, context) {
var results = []; var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value; if (!iterator.call(context, value, index, list)) results[results.length] = value;
}); });
@@ -161,8 +169,9 @@
// Aliased as `all`. // Aliased as `all`.
_.every = _.all = function(obj, iterator, context) { _.every = _.all = function(obj, iterator, context) {
iterator = iterator || _.identity; iterator = iterator || _.identity;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
var result = true; var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker; if (!(result = result && iterator.call(context, value, index, list))) return breaker;
}); });
@@ -174,8 +183,9 @@
// Aliased as `any`. // Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) { var any = _.some = _.any = function(obj, iterator, context) {
iterator = iterator || _.identity; iterator = iterator || _.identity;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
var result = false; var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
if (result = iterator.call(context, value, index, list)) return breaker; if (result = iterator.call(context, value, index, list)) return breaker;
}); });
@@ -185,8 +195,9 @@
// Determine if a given value is included in the array or object using `===`. // Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`. // Aliased as `contains`.
_.include = _.contains = function(obj, target) { _.include = _.contains = function(obj, target) {
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
var found = false; var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
any(obj, function(value) { any(obj, function(value) {
if (found = value === target) return true; if (found = value === target) return true;
}); });
@@ -346,6 +357,7 @@
// item in an array, or -1 if the item is not included in the array. // item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available. // Delegates to **ECMAScript 5**'s native `indexOf` if available.
_.indexOf = function(array, item) { _.indexOf = function(array, item) {
if (array == null) return -1;
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
return -1; return -1;
@@ -354,6 +366,7 @@
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) { _.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length; var i = array.length;
while (i--) if (array[i] === item) return i; while (i--) if (array[i] === item) return i;