Last active
February 20, 2020 22:46
-
-
Save dfkaye/e264263a88796199671402e7d13b08f5 to your computer and use it in GitHub Desktop.
re-implement array.map() as a standalone function (inspired by Nick Scialli)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 15 Feb 2020 | |
// re-implement array map as a standalone function | |
// inspired by Nick Scialli tweet | |
// https://twitter.com/nas5w/status/1228686339257835520 | |
var map = function(arr, fn, thisArg) { | |
// Guard against missing arguments | |
if (typeof fn != 'function') { | |
// Messages vary across browsers - this is the chrome message | |
throw new TypeError("undefined is not a function"); | |
} | |
var result = []; | |
for (var i = 0; i < arr.length; ++i) { | |
// Empty slots have no index in the array | |
var empty = !(i in arr); | |
if (empty) { | |
// Concatenate a new empty slot into the result | |
result = result.concat([,]) | |
} else { | |
// Ordinary course | |
result.push(fn.call(thisArg, arr[i], i, arr)); | |
} | |
} | |
return result; | |
}; | |
/* TEST IT OUT */ | |
var arr = ['a', 1, false, undefined, /* empty slot */, null, NaN]; | |
var fn = function(value, index, arr) { | |
console.info({ value: value, index: index, hasThis: this === arr }); | |
return value; | |
}; | |
/* thisArg no present in call */ | |
var noThis = { | |
builtin: arr.map(fn), | |
custom: map(arr, fn), | |
}; | |
/* | |
// Should see info statements in console... | |
{value: "a", index: 0, hasThis: false} | |
{value: 1, index: 1, hasThis: false} | |
{value: false, index: 2, hasThis: false} | |
{value: undefined, index: 3, hasThis: false} | |
{value: null, index: 5, hasThis: false} | |
{value: NaN, index: 6, hasThis: false} | |
{value: "a", index: 0, hasThis: false} | |
{value: 1, index: 1, hasThis: false} | |
{value: false, index: 2, hasThis: false} | |
{value: undefined, index: 3, hasThis: false} | |
{value: null, index: 5, hasThis: false} | |
{value: NaN, index: 6, hasThis: false} | |
*/ | |
console.log(noThis.builtin); | |
// ["a", 1, false, undefined, empty, null, NaN] | |
console.warn(noThis.custom); | |
// ["a", 1, false, undefined, empty, null, NaN] | |
/* thisArg provided */ | |
var withThis = { | |
builtin: arr.map(fn, arr), | |
custom: map(arr, fn, arr), | |
}; | |
/* | |
// Should see info statements in console... | |
{value: "a", index: 0, hasThis: true} | |
{value: 1, index: 1, hasThis: true} | |
{value: false, index: 2, hasThis: true} | |
{value: undefined, index: 3, hasThis: true} | |
{value: null, index: 5, hasThis: true} | |
{value: NaN, index: 6, hasThis: true} | |
{value: "a", index: 0, hasThis: true} | |
{value: 1, index: 1, hasThis: true} | |
{value: false, index: 2, hasThis: true} | |
{value: undefined, index: 3, hasThis: true} | |
{value: null, index: 5, hasThis: true} | |
{value: NaN, index: 6, hasThis: true} | |
*/ | |
console.log(withThis.builtin); | |
// ["a", 1, false, undefined, empty, null, NaN] | |
console.warn(withThis.custom); | |
// ["a", 1, false, undefined, empty, null, NaN] | |
/* Missing callback function argument */ | |
try { arr.map() } | |
catch (e) { console.error(e) } | |
// TypeError: (messages differ across browsers) | |
try { map() } | |
catch (e) { console.warn(e.message.includes('undefined is not a function')) } | |
// true - this is the chrome implementation | |
/* Applying to a non-array should return an empty array */ | |
console.log('builtin length:', [].map.call(0, fn).length); | |
// 0 | |
console.warn('custom length:', map(0, fn).length); | |
// 0 | |
/* | |
...and finally, the array-map trick | |
see https://gist.github.com/dfkaye/61462c89ae0a10c7829d03f616f02882 | |
*/ | |
var array = [" brad ", " 1 "]; | |
console.log(array.join(',')); | |
// brad , 1 | |
var trick = array.map(Function.prototype.call, String.prototype.trim); | |
console.log(trick.join(',')); | |
// brad,1 | |
var trickWithMap = map(array, Function.prototype.call, String.prototype.trim); | |
console.log(trickWithMap.join(',')); | |
// brad,1 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 20 Feb 2020 | |
// alternate versions with error reporting, | |
// empty slot filtering, | |
// iteration over copy of array | |
var iter = function(array, fn, thisArg) { | |
var errors = []; | |
if ({}.toString.call(array) !== '[object Array]') { | |
errors.push( "first argument must be an array" ); | |
} | |
if (typeof fn !== 'function') { | |
errors.push( "second argument must be a function" ); | |
} | |
if (errors.length) { | |
return { errors, array, fn } | |
} | |
var results = []; | |
// Own keys filters out the empty slots. | |
var keys = Object.keys(array); | |
// Work on a copy | |
var slice = array.slice(); | |
var k, result; | |
while(k = keys.shift()) { | |
result = fn.call(thisArg || slice, slice[k], k, slice); | |
results.push(result); | |
} | |
return results; | |
}; | |
/* TEST IT OUT */ | |
var array = ["a", 1, false, undefined, , null, NaN]; | |
var results = [ | |
{ array: void 0, function: void 0 }, | |
{ array: void 0, function: function(v, i) { return v; } }, | |
{ array: array, function: void 0 }, | |
{ array: { length: 12 }, function: function(v, i) { return v; } }, | |
{ array: array, function: function(v, i) { return v; } }, | |
{ array: ["this"], function: function(v, i) { return this; } }, | |
{ array: ["thisArg"], function: function(v, i) { return this; }, thisArg: { name: 'test' } } | |
].map(function(test) { | |
return iter(test.array, test.function, test.thisArg); | |
}); | |
console.log(JSON.stringify(results, null, 2)); | |
/* | |
[ | |
{ | |
"errors": [ | |
"first argument must be an array", | |
"second argument must be a function" | |
] | |
}, | |
{ | |
"errors": [ | |
"first argument must be an array" | |
] | |
}, | |
{ | |
"errors": [ | |
"second argument must be a function" | |
], | |
"array": [ | |
"a", | |
1, | |
false, | |
null, | |
null, | |
null, | |
null | |
] | |
}, | |
{ | |
"errors": [ | |
"first argument must be an array" | |
], | |
"array": { | |
"length": 12 | |
} | |
}, | |
[ | |
"a", | |
1, | |
false, | |
null, | |
null, | |
null | |
], | |
[ | |
[ | |
"this" | |
] | |
], | |
[ | |
{ | |
"name": "test" | |
} | |
] | |
] | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment