Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Avoid calling Array.prototype ES5 natives and fixes some edge-cases. …

…closes #57

Squashed commit of the following:

commit 9a5261c
Author: millermedeiros <miller@millermedeiros.com>
Date:   Sat Aug 11 00:09:32 2012 -0300

    moar tests for filter

commit 2ea1c17
Author: millermedeiros <miller@millermedeiros.com>
Date:   Thu Jul 26 14:03:16 2012 -0300

    improve array/forEach spec for sparse arrays and add notes about edge cases. see #gh-57

commit cad78a7
Author: millermedeiros <miller@millermedeiros.com>
Date:   Thu Jul 26 11:41:20 2012 -0300

    Avoid using Array.prototype native methods. See #gh-57

    Also fixed bugs reported by @jdalton on same issue:

     - `every` iterate in correct order (forward).
     - `map` make sure it works properly with sparse arrays.
     - `indexOf` and `lastIndexOf` make it work on sparse arrays.
     - `reduce` and `reduceRight` allow explicit initial value of `undefined`.

    Improved specs to cover more edge cases.
  • Loading branch information...
commit a23f91857bc068535257bfbb04c0022ffbb558a4 1 parent b3d3ab8
@millermedeiros authored
View
35 src/array/every.js
@@ -1,26 +1,23 @@
define(function () {
/**
- * ES5 Array.every
- * @version 0.2.1 (2011/11/25)
+ * Array every
+ * @version 0.3.0 (2012/07/26)
*/
- var every = Array.prototype.every?
- function (arr, callback, thisObj) {
- return arr.every(callback, thisObj);
- } :
- function (arr, callback, thisObj) {
- var result = true,
- n = arr.length >>> 0;
- while (n--) {
- //according to spec callback should only be called for
- //existing items
- if ( n in arr && !callback.call(thisObj, arr[n], n, arr) ) {
- result = false;
- break;
- }
- }
- return result;
- };
+ function every(arr, callback, thisObj) {
+ var result = true,
+ i = -1,
+ n = arr.length >>> 0;
+ while (++i < n) {
+ //according to spec callback should only be called for
+ //existing items
+ if ( i in arr && !callback.call(thisObj, arr[i], i, arr) ) {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
return every;
});
View
26 src/array/filter.js
@@ -1,22 +1,18 @@
define(['./forEach'], function (forEach) {
/**
- * ES5 Array.filter
- * @version 0.3.0 (2011/11/15)
+ * Array filter
+ * @version 0.4.0 (2012/07/26)
*/
- var filter = Array.prototype.filter?
- function (arr, callback, thisObj) {
- return arr.filter(callback, thisObj);
- } :
- function (arr, callback, thisObj) {
- var results = [];
- forEach(arr, function (val, i, arr) {
- if ( callback.call(thisObj, val, i, arr) ) {
- results.push(val);
- }
- });
- return results;
- };
+ function filter(arr, callback, thisObj) {
+ var results = [];
+ forEach(arr, function (val, i, arr) {
+ if ( callback.call(thisObj, val, i, arr) ) {
+ results.push(val);
+ }
+ });
+ return results;
+ }
return filter;
View
28 src/array/forEach.js
@@ -1,22 +1,20 @@
define(function () {
/**
- * ES5 Array.forEach
- * @version 0.3.1 (2011/11/25)
+ * Array forEach
+ * @version 0.4.0 (2012/07/26)
*/
- var forEach = Array.prototype.forEach?
- function (arr, callback, thisObj) {
- arr.forEach(callback, thisObj);
- } :
- function (arr, callback, thisObj) {
- for (var i = 0, n = arr.length >>> 0; i < n; i++) {
- //according to spec callback should only be called for
- //existing items
- if (i in arr) {
- callback.call(thisObj, arr[i], i, arr);
- }
- }
- };
+ function forEach(arr, callback, thisObj) {
+ var i = -1,
+ n = arr.length >>> 0;
+ while (++i < n) {
+ //according to spec callback should only be called for
+ //existing items
+ if (i in arr) {
+ callback.call(thisObj, arr[i], i, arr);
+ }
+ }
+ }
return forEach;
View
32 src/array/indexOf.js
@@ -1,24 +1,22 @@
define(function () {
/**
- * ES5 Array.indexOf
- * @version 0.2.1 (2011/11/25)
+ * Array.indexOf
+ * @version 0.3.0 (2012/07/26)
*/
- var indexOf = Array.prototype.indexOf?
- function (arr, item, fromIndex) {
- return arr.indexOf(item, fromIndex);
- } :
- function (arr, item, fromIndex) {
- fromIndex = fromIndex || 0;
- var n = arr.length >>> 0,
- i = fromIndex < 0? n + fromIndex : fromIndex;
- for (; i < n; i++) {
- if (arr[i] === item) {
- return i;
- }
- }
- return -1;
- };
+ function indexOf(arr, item, fromIndex) {
+ fromIndex = fromIndex || 0;
+ var n = arr.length >>> 0,
+ i = fromIndex < 0? n + fromIndex : fromIndex;
+ while (i < n) {
+ //it should skip sparse items
+ if (i in arr && arr[i] === item) {
+ return i;
+ }
+ i += 1;
+ }
+ return -1;
+ }
return indexOf;
});
View
33 src/array/lastIndexOf.js
@@ -1,25 +1,22 @@
define(function () {
/**
- * ES5 Array.lastIndexOf
- * @version 0.2.1 (2011/11/25)
+ * Array lastIndexOf
+ * @version 0.3.0 (2012/07/26)
*/
- var lastIndexOf = Array.prototype.lastIndexOf?
- function (arr, item, fromIndex) {
- return fromIndex == null? arr.lastIndexOf(item) : arr.lastIndexOf(item, fromIndex);
- } :
- function (arr, item, fromIndex) {
- var len = arr.length >>> 0;
- fromIndex = (fromIndex == null || fromIndex >= len)? len - 1 : fromIndex;
- fromIndex = (fromIndex < 0)? len + fromIndex : fromIndex;
- while (fromIndex >= 0) {
- if (arr[fromIndex] === item) {
- return fromIndex;
- }
- fromIndex--;
- }
- return -1;
- };
+ function lastIndexOf(arr, item, fromIndex) {
+ var len = arr.length >>> 0;
+ fromIndex = (fromIndex == null || fromIndex >= len)? len - 1 : fromIndex;
+ fromIndex = (fromIndex < 0)? len + fromIndex : fromIndex;
+ while (fromIndex >= 0) {
+ // it should skip sparse items
+ if (fromIndex in arr && arr[fromIndex] === item) {
+ return fromIndex;
+ }
+ fromIndex--;
+ }
+ return -1;
+ }
return lastIndexOf;
});
View
23 src/array/map.js
@@ -1,20 +1,17 @@
define(['./forEach'], function (forEach) {
/**
- * ES5 Array.map
- * @version 0.2.0 (2011/11/15)
+ * Array map
+ * @version 0.3.0 (2012/07/26)
*/
- var map = Array.prototype.map?
- function (arr, callback, thisObj) {
- return arr.map(callback, thisObj);
- } :
- function (arr, callback, thisObj) {
- var results = [];
- forEach(arr, function (val, i, arr) {
- results[results.length] = callback.call(thisObj, val, i, arr);
- });
- return results;
- };
+ function map(arr, callback, thisObj) {
+ // need to copy arr.length because of sparse arrays
+ var results = new Array(arr.length);
+ forEach(arr, function (val, i, arr) {
+ results[i] = callback.call(thisObj, val, i, arr);
+ });
+ return results;
+ }
return map;
});
View
41 src/array/reduce.js
@@ -1,32 +1,29 @@
define(['./forEach'], function (forEach) {
/**
- * ES5 Array.reduce
- * @version 0.2.0 (2011/11/15)
+ * Array reduce
+ * @version 0.3.0 (2012/07/26)
*/
- var reduce = Array.prototype.reduce?
- function (arr, fn, initVal) {
- return initVal == null? arr.reduce(fn) : arr.reduce(fn, initVal);
- } :
- function (arr, fn, initVal) {
- var hasInit = typeof initVal !== 'undefined',
- result = initVal;
+ function reduce(arr, fn, initVal) {
+ // check for args.length since initVal might be "undefined" see #gh-57
+ var hasInit = arguments.length > 2,
+ result = initVal;
- if (!arr.length && !hasInit) {
- throw new Error('reduce of empty array with no initial value');
- }
+ if (!arr.length && !hasInit) {
+ throw new Error('reduce of empty array with no initial value');
+ }
- forEach(arr, function (val, i, arr) {
- if (! hasInit) {
- result = val;
- hasInit = true;
- } else {
- result = fn(result, val, i, arr);
- }
- });
+ forEach(arr, function (val, i, arr) {
+ if (! hasInit) {
+ result = val;
+ hasInit = true;
+ } else {
+ result = fn(result, val, i, arr);
+ }
+ });
- return result;
- };
+ return result;
+ }
return reduce;
});
View
52 src/array/reduceRight.js
@@ -1,36 +1,34 @@
define(function () {
/**
- * ES5 Array.reduceRight
- * @version 0.2.1 (2011/11/25)
+ * Array reduceRight
+ * @version 0.3.0 (2012/07/26)
*/
- var reduceRight = Array.prototype.reduceRight?
- function (arr, fn, initVal) {
- return initVal == null? arr.reduceRight(fn) : arr.reduceRight(fn, initVal);
- } :
- function (arr, fn, initVal) {
- var hasInit = typeof initVal !== 'undefined',
- result = initVal,
- i = arr.length >>> 0,
- val;
+ function reduceRight(arr, fn, initVal) {
+ // check for args.length since initVal might be "undefined" see #gh-57
+ var hasInit = arguments.length > 2,
+ result = initVal,
+ i = arr.length >>> 0,
+ val;
- if (!i && !hasInit) {
- throw new Error('reduce of empty array with no initial value');
- }
+ if (!i && !hasInit) {
+ throw new Error('reduce of empty array with no initial value');
+ }
- while (--i >= 0) {
- val = arr[i];
- if (typeof val !== 'undefined') {
- if (! hasInit) {
- result = val;
- hasInit = true;
- } else {
- result = fn(result, val, i, arr);
- }
- }
- }
- return result;
- };
+ while (--i >= 0) {
+ // skip sparse items but keep "undefined" items
+ if (i in arr) {
+ val = arr[i];
+ if (! hasInit) {
+ result = val;
+ hasInit = true;
+ } else {
+ result = fn(result, val, i, arr);
+ }
+ }
+ }
+ return result;
+ }
return reduceRight;
});
View
37 src/array/some.js
@@ -1,28 +1,23 @@
define(function (forEach) {
/**
- * ES5 Array.some
- * @version 0.2.2 (2012/06/07)
+ * Array some
+ * @version 0.3.0 (2012/07/26)
*/
- var some = Array.prototype.some?
- function (arr, callback, thisObj) {
- return arr.some(callback, thisObj);
- } :
- function (arr, callback, thisObj) {
- var result = false,
- n = arr.length,
- i = 0;
- while (i < n) {
- //according to spec callback should only be called for
- //existing items
- if ( i in arr && callback.call(thisObj, arr[i], i, arr) ) {
- result = true;
- break;
- }
- i += 1;
- }
- return result;
- };
+ function some(arr, callback, thisObj) {
+ var result = false,
+ i = -1,
+ n = arr.length >>> 0;
+ while (++i < n) {
+ //according to spec callback should only be called for
+ //existing items
+ if ( i in arr && callback.call(thisObj, arr[i], i, arr) ) {
@jdalton
jdalton added a note

if you ditch sparse array support you can ditch the i in arr &&.
Also it's really cool I got summoned here by the mention in the commit message :D

@millermedeiros Owner

I created a new issue for it (#64) will definitely do it later. Don't want things to have different behavior on IE 7-8, want to avoid headaches even tho sparse arrays aren't that common (another reason to drop support).

@jdalton
jdalton added a note

word!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
return some;
});
View
15 tests/spec/array/spec-every.js
@@ -30,10 +30,23 @@ define(['src/array/every'], function (every) {
});
it('should work on empty arrays', function () {
- //t is vacuously true that all elements of the empty set satisfy any given condition.
+ //it is vacuously true that all elements of the empty set satisfy any given condition.
expect( every([], isEven) ).toBe( true );
});
+ it('should loop forwards to avoid undesired behavior', function () {
+ // not that the loop order should matter on a truth check over all
+ // elements
+ var a1 = [1, 3, 7];
+ var result = [];
+ expect( every(a1, function(val, i, arr){
+ result.push(val);
+ return val !== 8;
+ }) ).toEqual( true );
+ expect( result ).toEqual( [1, 3, 7] );
+
+ });
+
});
View
29 tests/spec/array/spec-filter.js
@@ -8,12 +8,33 @@ define(['src/array/filter'], function (filter) {
return (val % 2) !== 0;
});
- expect( result.length ).toEqual( 3 );
expect( items.length ).toEqual( 5 ); //make sure it doesn't replace original array
+ expect( result ).toEqual( [1, 3, 5] );
+ });
+
+ it('should support sparse arrays', function () {
+ var items = new Array(6);
+ items[2] = 3;
+ items[5] = 8;
+
+ var result = filter(items, function(val, i, arr){
+ expect( arr ).toBe( items );
+ expect( val ).toBe( items[i] );
+ expect( i ).not.toBe( 4 ); // make sure it skips sparse items
+ return val % 2 === 0;
+ });
+
+ expect( result ).toEqual( [8] );
+
+ });
+
+ it('should return empty array if no items match', function () {
+ var items = [1,2,3,4,5];
+ var result = filter(items, function(val, i, arr){
+ return false;
+ });
- expect( result[0] ).toEqual( 1 );
- expect( result[1] ).toEqual( 3 );
- expect( result[2] ).toEqual( 5 );
+ expect( result ).toEqual( [] );
});
});
View
62 tests/spec/array/spec-forEach.js
@@ -15,6 +15,68 @@ define(['src/array/forEach'], function (forEach) {
expect( result ).toBe( 15 );
});
+ describe('sparse arrays', function () {
+
+ it('should support sparse arrays', function () {
+ var arr1 = new Array(6);
+ arr1[2] = 3;
+ arr1[5] = 8;
+ arr1[10] = undefined; // it's a trap!
+
+ var result = [];
+
+ forEach(arr1, function(val, i, arr){
+ expect( arr ).toBe( arr1 );
+ expect( val ).toBe( arr1[i] );
+ expect( i ).not.toBe( 4 ); // make sure it skips sparse items
+ result.push(val);
+ });
+
+ expect( result[0] ).toEqual( 3 );
+ expect( result[1] ).toEqual( 8 );
+ expect( result[2] ).toEqual( undefined );
+ });
+
+ it('should support arrays with `undefined` items - this will fail on IE 6-8!!!', function () {
+ // IMPORTANT!
+ // ----------
+ // this is considered sparse on IE 6-8, there is no way to ensure
+ // proper behavior for this case everywhere!!!
+ // IE will consider the `undefined` as "empty" so NEVER do that
+ // on your code!
+ // See: #gh-57
+
+ var arr2 = [2, void(0), 4];
+ var r2 = [];
+
+ forEach(arr2, function(val, i, arr){
+ expect( arr ).toBe( arr2 );
+ expect( val ).toBe( arr2[i] );
+ r2.push(val);
+ });
+
+ expect( r2[0] ).toEqual( 2 );
+ expect( r2[1] ).toEqual( void(0) );
+ expect( r2[2] ).toEqual( 4 );
+ });
+
+ it('should support arrays with missing items', function () {
+ var arr3 = [5, ,7];
+ var r3 = [];
+
+ forEach(arr3, function(val, i, arr){
+ expect( arr ).toBe( arr3 );
+ expect( val ).toBe( arr3[i] );
+ r3.push(val);
+ });
+
+ expect( r3[0] ).toEqual( 5 );
+ expect( r3[1] ).toEqual( 7 );
+ expect( r3.length ).toEqual( 2 );
+ });
+
+ });
+
});
});
View
2  tests/spec/array/spec-indexOf.js
@@ -21,11 +21,13 @@ define(['src/array/indexOf'], function(indexOf){
arr[3] = 'a';
arr[6] = 2;
arr[8] = 'b';
+ arr[10] = undefined;
expect( idx(arr, 1) ).toEqual( 1 );
expect( idx(arr, 'a') ).toEqual( 3 );
expect( idx(arr, 2) ).toEqual( 6 );
expect( idx(arr, 'b') ).toEqual( 8 );
+ expect( idx(arr, undefined) ).toEqual( 10 );
expect( idx(arr, 'foo') ).toEqual( -1 );
});
View
2  tests/spec/array/spec-lastIndexOf.js
@@ -19,6 +19,7 @@ define(['src/array/lastIndexOf'], function (lastIndexOf) {
var arr = [];
arr[1] = 1;
arr[3] = 'a';
+ arr[4] = undefined; // it's a trap!
arr[6] = 2;
arr[8] = 'b';
@@ -26,6 +27,7 @@ define(['src/array/lastIndexOf'], function (lastIndexOf) {
expect( lastIdx(arr, 'a') ).toEqual( 3 );
expect( lastIdx(arr, 2) ).toEqual( 6 );
expect( lastIdx(arr, 'b') ).toEqual( 8 );
+ expect( lastIdx(arr, undefined) ).toEqual( 4 );
expect( lastIdx(arr, 'foo') ).toEqual( -1 );
});
View
20 tests/spec/array/spec-map.js
@@ -8,11 +8,21 @@ define(['src/array/map'], function (map) {
return val + i;
});
- expect( r[0] ).toBe( 1 );
- expect( r[1] ).toBe( 3 );
- expect( r[2] ).toBe( 5 );
- expect( r[3] ).toBe( 7 );
- expect( r[4] ).toBe( 9 );
+ expect( r ).toEqual( [1, 3, 5, 7, 9] );
+ });
+
+ it('should handle sparse arrays', function () {
+ function toOne(val, i){ return 1; }
+
+ var base = new Array(3);
+ var r = map(base, toOne);
+ // it looks weird but that is the correct behavior!!
+ expect( r ).toEqual( [undefined, undefined, undefined] );
+
+ base[5] = 'foo';
+
+ r = map(base, toOne);
+ expect( r ).toEqual( [undefined, undefined, undefined, undefined, undefined, 1] );
});
});
View
41 tests/spec/array/spec-reduce.js
@@ -5,18 +5,25 @@ define(['src/array/reduce'], function (reduce) {
it('should reduce array into a single value', function () {
var arr = [1, 2, 3, 4];
+ var compare1 = [];
+ var compare2 = [];
function sum(prev, cur, idx, arr) {
+ compare1.push(prev);
return prev + cur;
}
function mult(prev, cur, idx, arr) {
+ compare2.push(prev);
return prev * cur;
}
expect( reduce(arr, sum) ).toBe( 10 );
expect( reduce(arr, mult) ).toBe( 24 );
+ expect( compare1 ).toEqual( [1, 3, 6] );
+ expect( compare2 ).toEqual( [1, 2, 6] );
+
});
it('should allow init value', function () {
@@ -64,7 +71,39 @@ define(['src/array/reduce'], function (reduce) {
expect( reduce([], sum, 10) ).toBe(10);
});
- });
+ it('should work over sparse arrays', function () {
+ function specialSum(prev, cur, i, arr){
+ var a = prev == null? 1 : prev;
+ var b = cur == null? 1: cur;
+ return a + b;
+ }
+
+ var base = [1, 5];
+ base[7] = 4;
+ base[10] = undefined;
+
+ expect( reduce(base, specialSum) ).toEqual( 11 );
+ expect( reduce(base, specialSum, 2) ).toEqual( 13 );
+ });
+
+
+ it('should allow "undefined" as initial value', function () {
+ // thx @jdalton for catching this one see #gh-57
+ var base = [1, 2, 3];
+ var compare = [];
+
+ var r = reduce(base, function(prev, cur, i, arr){
+ compare.push(prev);
+ return prev == null? cur : prev * cur;
+ }, undefined);
+
+ expect( r ).toBe( 6 );
+ expect( compare ).toEqual( [undefined, 1, 2] );
+
+ });
+
+
+ });
});
View
41 tests/spec/array/spec-reduceRight.js
@@ -5,18 +5,25 @@ define(['src/array/reduceRight'], function (reduceRight) {
it('should reduce array into a single value', function () {
var arr = [1, 2, 3, 4];
+ var compare1 = [];
+ var compare2 = [];
function sum(prev, cur, idx, arr) {
+ compare1.push(prev);
return prev + cur;
}
function mult(prev, cur, idx, arr) {
+ compare2.push(prev);
return prev * cur;
}
expect( reduceRight(arr, sum) ).toBe( 10 );
expect( reduceRight(arr, mult) ).toBe( 24 );
+ expect( compare1 ).toEqual( [4, 7, 9] );
+ expect( compare2 ).toEqual( [4, 12, 24] );
+
});
it('should allow init value', function () {
@@ -64,7 +71,39 @@ define(['src/array/reduceRight'], function (reduceRight) {
expect( reduceRight([], sum, 10) ).toBe(10);
});
- });
+ it('should work over sparse arrays', function () {
+ function specialSum(prev, cur, i, arr){
+ var a = prev == null? 1 : prev;
+ var b = cur == null? 1: cur;
+ return a + b;
+ }
+
+ var base = [1, 5];
+ base[7] = 4;
+ base[10] = undefined;
+
+ expect( reduceRight(base, specialSum) ).toEqual( 11 );
+ expect( reduceRight(base, specialSum, 2) ).toEqual( 13 );
+ });
+
+
+ it('should allow "undefined" as initial value', function () {
+ // thx @jdalton for catching this one see #gh-57
+ var base = [1, 2, 3];
+ var compare = [];
+
+ var r = reduceRight(base, function(prev, cur, i, arr){
+ compare.push(prev);
+ return prev == null? cur : prev * cur;
+ }, undefined);
+
+ expect( r ).toBe( 6 );
+ expect( compare ).toEqual( [undefined, 3, 6] );
+
+ });
+
+
+ });
});
Please sign in to comment.
Something went wrong with that request. Please try again.