Skip to content

Commit 978efc8

Browse files
Update the Array#indexOf and Array#lastIndexOf polyfills to be ES5-compliant. (sofish, Andrew Dupont)
1 parent c5e7547 commit 978efc8

File tree

2 files changed

+102
-21
lines changed

2 files changed

+102
-21
lines changed

src/prototype/lang/array.js

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -423,27 +423,74 @@ Array.from = $A;
423423
* // -> -1 (not found, 1 !== '1')
424424
**/
425425
function indexOf(item, i) {
426-
i || (i = 0);
427-
var length = this.length;
428-
if (i < 0) i = length + i;
429-
for (; i < length; i++)
430-
if (this[i] === item) return i;
426+
if (this == null) throw new TypeError();
427+
428+
var array = Object(this), length = array.length >>> 0;
429+
if (length === 0) return -1;
430+
431+
// The rules for the `fromIndex` argument are tricky. Let's follow the
432+
// spec line-by-line.
433+
i = Number(i);
434+
if (isNaN(i)) {
435+
i = 0;
436+
} else if (i !== 0 && isFinite(i)) {
437+
// Equivalent to ES5's `ToInteger` operation.
438+
i = (i > 0 ? 1 : -1) * Math.floor(Math.abs(i));
439+
}
440+
441+
// If the search index is greater than the length of the array,
442+
// return -1.
443+
if (i > length) return -1;
444+
445+
// If the search index is negative, take its absolute value, subtract it
446+
// from the length, and make that the new search index. If it's still
447+
// negative, make it 0.
448+
var k = i >= 0 ? i : Math.max(length - Math.abs(i), 0);
449+
for (; k < length; k++)
450+
if (k in array && array[k] === item) return k;
431451
return -1;
432452
}
453+
433454

434455
/** related to: Array#indexOf
435456
* Array#lastIndexOf(item[, offset]) -> Number
436457
* - item (?): A value that may or may not be in the array.
437-
* - offset (Number): The number of items at the end to skip before beginning
438-
* the search.
458+
* - offset (Number): The number of items at the end to skip before
459+
* beginning the search.
439460
*
440-
* Returns the position of the last occurrence of `item` within the array &mdash; or
441-
* `-1` if `item` doesn't exist in the array.
461+
* Returns the position of the last occurrence of `item` within the
462+
* array &mdash; or `-1` if `item` doesn't exist in the array.
442463
**/
443464
function lastIndexOf(item, i) {
444-
i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
445-
var n = this.slice(0, i).reverse().indexOf(item);
446-
return (n < 0) ? n : i - n - 1;
465+
if (this == null) throw new TypeError();
466+
467+
var array = Object(this), length = array.length >>> 0;
468+
if (length === 0) return -1;
469+
470+
// The rules for the `fromIndex` argument are tricky. Let's follow the
471+
// spec line-by-line.
472+
if (!Object.isUndefined(i)) {
473+
i = Number(i);
474+
if (isNaN(i)) {
475+
i = 0;
476+
} else if (i !== 0 && isFinite(i)) {
477+
// Equivalent to ES5's `ToInteger` operation.
478+
i = (i > 0 ? 1 : -1) * Math.floor(Math.abs(i));
479+
}
480+
} else {
481+
i = length;
482+
}
483+
484+
// If fromIndex is positive, clamp it to the last index in the array;
485+
// if it's negative, subtract its absolute value from the array's length.
486+
var k = i >= 0 ? Math.min(i, length - 1) :
487+
length - Math.abs(i);
488+
489+
// (If fromIndex is still negative, it'll bypass this loop altogether and
490+
// return -1.)
491+
for (; k >= 0; k--)
492+
if (k in array && array[k] === item) return k;
493+
return -1;
447494
}
448495

449496
// Replaces a built-in function. No PDoc needed.

test/unit/array_test.js

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ new Test.Unit.Runner({
9696
this.assertEqual(0, [1,2,1].indexOf(1));
9797
this.assertEqual(2, [1,2,1].indexOf(1, -1));
9898
this.assertEqual(1, [undefined,null].indexOf(null));
99+
100+
// ES5 compatibility tests.
101+
var undef;
102+
var array = [1, 2, 3, 4, 5, undef, 6, 7, 1, 2, 3];
103+
104+
this.assertEqual(2, array.indexOf(3, -47),
105+
"large negative value for fromIndex");
106+
this.assertEqual(10, array.indexOf(3, 4));
107+
this.assertEqual(10, array.indexOf(3, -5))
108+
this.assertEqual(2, array.indexOf(3, {}),
109+
"nonsensical value for fromIndex");
110+
this.assertEqual(2, array.indexOf(3, ""),
111+
"nonsensical value for fromIndex");
112+
this.assertEqual(-1, array.indexOf(3, 41),
113+
"fromIndex value larger than the length of the array");
99114
},
100115

101116
testLastIndexOf: function(){
@@ -188,18 +203,37 @@ new Test.Unit.Runner({
188203

189204
this.assertIdentical(1, Array.prototype.concat.length);
190205

191-
this.assertEnumEqual([0, 1], [0, 1].concat());
192-
this.assertIdentical(2, [0, 1].concat().length);
206+
this.assertEnumEqual(
207+
[0, 1],
208+
[0, 1].concat(),
209+
"test 2"
210+
);
211+
this.assertIdentical(2, [0, 1].concat().length, "test 3");
193212

194-
this.assertEnumEqual([0, 1, 2, 3, 4], [].concat([0, 1], [2, 3, 4]));
195-
this.assertIdentical(5, [].concat([0, 1], [2, 3, 4]).length);
213+
this.assertEnumEqual(
214+
[0, 1, 2, 3, 4],
215+
[].concat([0, 1], [2, 3, 4]),
216+
"test 4"
217+
);
218+
this.assertIdentical(5, [].concat([0, 1], [2, 3, 4]).length, "test 5");
196219

197-
this.assertEnumEqual([0, x, 1, 2, true, "NaN"], [0].concat(x, [1, 2], true, "NaN"));
198-
this.assertIdentical(6, [0].concat(x, [1, 2], true, "NaN").length);
220+
this.assertEnumEqual([0, x, 1, 2, true, "NaN"], [0].concat(x, [1, 2], true, "NaN"), "test 6");
221+
this.assertIdentical(6, [0].concat(x, [1, 2], true, "NaN").length, "test 7");
222+
223+
// These tests will fail in older IE because of the trailing comma.
224+
// Nothing we can do about that, so just skip them and let the user know.
225+
if ([,].length === 2) {
226+
this.info("NOTE: Old versions of IE don't like trailing commas in " +
227+
"arrays. Skipping some tests.");
228+
} else {
229+
this.assertEnumEqual([undefined, 1, undefined], [,1].concat([], [,]),
230+
"concatenation behavior with a trailing comma (1)");
231+
this.assertIdentical(3, [,1].concat([], [,]).length,
232+
"concatenation behavior with a trailing comma (2)");
233+
}
234+
199235

200-
this.assertEnumEqual([undefined, 1, undefined], [,1].concat([], [,]));
201-
this.assertIdentical(3, [,1].concat([], [,]).length);
202-
this.assertEnumEqual([1], Object.keys([,1].concat([], [,])));
236+
this.assertEnumEqual([1], Object.keys([,1].concat([], [,])), "test 10");
203237

204238
// Check that Array.prototype.concat can be used in a generic way
205239
x.concat = Array.prototype.concat;

0 commit comments

Comments
 (0)