Last active
May 5, 2020 22:23
-
-
Save jayphelps/52ed7a4f1bcdd0108159c1d513964e3b to your computer and use it in GitHub Desktop.
For the lolz
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
// The spec might seem somewhat convoluted but it's because you can actually | |
// use reduce on anything array-like not just arrays. | |
// e.g. Array.prototype.reduce.call('abc', (acc, char) => char + acc); // "cba" | |
function reduce(callback, initialValue) { | |
// In "strict mode" calling this with either null or undefined will make | |
// this === null in either case, as per spec. | |
if (this === null) { | |
throw new TypeError('reduce called on null or undefined'); | |
} | |
// Spec says to do these before the typeof callback check. | |
// This actually could be observable from the outside, because o.length | |
// might be a getter! Chrome does this right, I checked! | |
const o = Object(this); | |
const len = Math.max(o.length | 0, 0); | |
if (typeof callback !== 'function') { | |
// Symbols (maybe others) will error if you try to interpolate them, | |
// but not if you first do String(callback). | |
throw new TypeError(`${String(callback)} is not a function`); | |
} | |
if (len === 0 && arguments.length < 2) { | |
throw new TypeError('Reduce of empty array with no initial value'); | |
} | |
let k = 0; | |
let accumulator; | |
// Important not to just do `initialValue === undefined` because | |
// undefined is a valid initialValue and not the same thing as | |
// omitting that argument. | |
if (arguments.length >= 2) { | |
accumulator = initialValue; | |
} else { | |
let kPresent = false; | |
while (!kPresent && k < len) { | |
// We're able to skip the `let Pk` stuff because JS will do the same thing | |
// under th hood with normal property accesses and I'm lazy right now. | |
// https://tc39.es/ecma262/#sec-topropertykey does the convertion: | |
// https://tc39.es/ecma262/#sec-tostring note this is not the same as obj.toString() ! | |
// This is effectively what the spec asks for. The spec's HasProperty(O, Pk) | |
// is NOT the same thing as o.hasOwnProperty(Pk) | |
// https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation | |
kPresent = k in o; | |
if (kPresent === true) { | |
accumulator = o[k]; | |
} | |
k = k + 1; | |
} | |
// I tried to hit this code path in Chrome's native array reduce, but I | |
// couldn't induce it. I might be misunderstanding the spec, or it just | |
// might be such an edge case that it doesn't deal with it. | |
if (kPresent === false) { | |
throw new TypeError(`No initial value and not array-like`); | |
} | |
} | |
while (k < len) { | |
// Skipping the 'Let Pk be ! ToString(k)' stuff again cause I'm lazy and | |
// it's rather boring. This works the same, I think. | |
const kPresent = k in o; | |
if (kPresent === true) { | |
const kValue = o[k]; | |
accumulator = callback.call(undefined, accumulator, kValue, k, o); | |
} | |
k = k + 1; | |
} | |
return accumulator; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment