Permalink
Browse files

Update the Array#indexOf and Array#lastIndexOf polyfills to be ES5-co…

…mpliant. (sofish, Andrew Dupont)
  • Loading branch information...
1 parent c5e7547 commit 978efc8e7fa02a3a7fd5123923bd4cb92c1d9975 @savetheclocktower savetheclocktower committed Mar 23, 2012
Showing with 102 additions and 21 deletions.
  1. +59 −12 src/prototype/lang/array.js
  2. +43 −9 test/unit/array_test.js
@@ -423,27 +423,74 @@ Array.from = $A;
* // -> -1 (not found, 1 !== '1')
**/
function indexOf(item, i) {
- i || (i = 0);
- var length = this.length;
- if (i < 0) i = length + i;
- for (; i < length; i++)
- if (this[i] === item) return i;
+ if (this == null) throw new TypeError();
+
+ var array = Object(this), length = array.length >>> 0;
+ if (length === 0) return -1;
+
+ // The rules for the `fromIndex` argument are tricky. Let's follow the
+ // spec line-by-line.
+ i = Number(i);
+ if (isNaN(i)) {
+ i = 0;
+ } else if (i !== 0 && isFinite(i)) {
+ // Equivalent to ES5's `ToInteger` operation.
+ i = (i > 0 ? 1 : -1) * Math.floor(Math.abs(i));
+ }
+
+ // If the search index is greater than the length of the array,
+ // return -1.
+ if (i > length) return -1;
+
+ // If the search index is negative, take its absolute value, subtract it
+ // from the length, and make that the new search index. If it's still
+ // negative, make it 0.
+ var k = i >= 0 ? i : Math.max(length - Math.abs(i), 0);
+ for (; k < length; k++)
+ if (k in array && array[k] === item) return k;
return -1;
}
+
/** related to: Array#indexOf
* Array#lastIndexOf(item[, offset]) -> Number
* - item (?): A value that may or may not be in the array.
- * - offset (Number): The number of items at the end to skip before beginning
- * the search.
+ * - offset (Number): The number of items at the end to skip before
+ * beginning the search.
*
- * Returns the position of the last occurrence of `item` within the array &mdash; or
- * `-1` if `item` doesn't exist in the array.
+ * Returns the position of the last occurrence of `item` within the
+ * array &mdash; or `-1` if `item` doesn't exist in the array.
**/
function lastIndexOf(item, i) {
- i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
- var n = this.slice(0, i).reverse().indexOf(item);
- return (n < 0) ? n : i - n - 1;
+ if (this == null) throw new TypeError();
+
+ var array = Object(this), length = array.length >>> 0;
+ if (length === 0) return -1;
+
+ // The rules for the `fromIndex` argument are tricky. Let's follow the
+ // spec line-by-line.
+ if (!Object.isUndefined(i)) {
+ i = Number(i);
+ if (isNaN(i)) {
+ i = 0;
+ } else if (i !== 0 && isFinite(i)) {
+ // Equivalent to ES5's `ToInteger` operation.
+ i = (i > 0 ? 1 : -1) * Math.floor(Math.abs(i));
+ }
+ } else {
+ i = length;
+ }
+
+ // If fromIndex is positive, clamp it to the last index in the array;
+ // if it's negative, subtract its absolute value from the array's length.
+ var k = i >= 0 ? Math.min(i, length - 1) :
+ length - Math.abs(i);
+
+ // (If fromIndex is still negative, it'll bypass this loop altogether and
+ // return -1.)
+ for (; k >= 0; k--)
+ if (k in array && array[k] === item) return k;
+ return -1;
}
// Replaces a built-in function. No PDoc needed.
View
@@ -96,6 +96,21 @@ new Test.Unit.Runner({
this.assertEqual(0, [1,2,1].indexOf(1));
this.assertEqual(2, [1,2,1].indexOf(1, -1));
this.assertEqual(1, [undefined,null].indexOf(null));
+
+ // ES5 compatibility tests.
+ var undef;
+ var array = [1, 2, 3, 4, 5, undef, 6, 7, 1, 2, 3];
+
+ this.assertEqual(2, array.indexOf(3, -47),
+ "large negative value for fromIndex");
+ this.assertEqual(10, array.indexOf(3, 4));
+ this.assertEqual(10, array.indexOf(3, -5))
+ this.assertEqual(2, array.indexOf(3, {}),
+ "nonsensical value for fromIndex");
+ this.assertEqual(2, array.indexOf(3, ""),
+ "nonsensical value for fromIndex");
+ this.assertEqual(-1, array.indexOf(3, 41),
+ "fromIndex value larger than the length of the array");
},
testLastIndexOf: function(){
@@ -188,18 +203,37 @@ new Test.Unit.Runner({
this.assertIdentical(1, Array.prototype.concat.length);
- this.assertEnumEqual([0, 1], [0, 1].concat());
- this.assertIdentical(2, [0, 1].concat().length);
+ this.assertEnumEqual(
+ [0, 1],
+ [0, 1].concat(),
+ "test 2"
+ );
+ this.assertIdentical(2, [0, 1].concat().length, "test 3");
- this.assertEnumEqual([0, 1, 2, 3, 4], [].concat([0, 1], [2, 3, 4]));
- this.assertIdentical(5, [].concat([0, 1], [2, 3, 4]).length);
+ this.assertEnumEqual(
+ [0, 1, 2, 3, 4],
+ [].concat([0, 1], [2, 3, 4]),
+ "test 4"
+ );
+ this.assertIdentical(5, [].concat([0, 1], [2, 3, 4]).length, "test 5");
- this.assertEnumEqual([0, x, 1, 2, true, "NaN"], [0].concat(x, [1, 2], true, "NaN"));
- this.assertIdentical(6, [0].concat(x, [1, 2], true, "NaN").length);
+ this.assertEnumEqual([0, x, 1, 2, true, "NaN"], [0].concat(x, [1, 2], true, "NaN"), "test 6");
+ this.assertIdentical(6, [0].concat(x, [1, 2], true, "NaN").length, "test 7");
+
+ // These tests will fail in older IE because of the trailing comma.
+ // Nothing we can do about that, so just skip them and let the user know.
+ if ([,].length === 2) {
+ this.info("NOTE: Old versions of IE don't like trailing commas in " +
+ "arrays. Skipping some tests.");
+ } else {
+ this.assertEnumEqual([undefined, 1, undefined], [,1].concat([], [,]),
+ "concatenation behavior with a trailing comma (1)");
+ this.assertIdentical(3, [,1].concat([], [,]).length,
+ "concatenation behavior with a trailing comma (2)");
+ }
+
- this.assertEnumEqual([undefined, 1, undefined], [,1].concat([], [,]));
- this.assertIdentical(3, [,1].concat([], [,]).length);
- this.assertEnumEqual([1], Object.keys([,1].concat([], [,])));
+ this.assertEnumEqual([1], Object.keys([,1].concat([], [,])), "test 10");
// Check that Array.prototype.concat can be used in a generic way
x.concat = Array.prototype.concat;

0 comments on commit 978efc8

Please sign in to comment.