Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active February 20, 2020 22:46
Show Gist options
  • Save dfkaye/e264263a88796199671402e7d13b08f5 to your computer and use it in GitHub Desktop.
Save dfkaye/e264263a88796199671402e7d13b08f5 to your computer and use it in GitHub Desktop.
re-implement array.map() as a standalone function (inspired by Nick Scialli)
// 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
// 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