parseInt parses an integer out of the beginning of a string: it skips leading whitespace, reads an optional sign, then consumes characters that are valid digits for a chosen base and stops at the first one that is not. That "stop at the first invalid character" behaviour is exactly what separates it from Number — Number('12px') is NaN, but parseInt('12px') is 12. You will rebuild the global parseInt from scratch as stringToNumber(str, radix), matching its behaviour closely enough that it agrees with the real thing on every input. The one twist worth knowing up front is the base, or radix: it can be given explicitly, defaulted to 10, or inferred as 16 from a leading 0x.
stringToNumber(str, radix)
// str — the value to parse (non-strings are coerced with String())
// radix — the base to read in, 2..36; 0 or omitted means base 10 with 0x inference
// returns an integer, or NaN when no digits could be read
stringToNumber(' -42'); // -42 (leading whitespace skipped, sign read)
stringToNumber('12px'); // 12 (stops at the first non-digit)
stringToNumber('0x1F'); // 31 (0x infers base 16)
stringToNumber('1010', 2); // 10 (explicit binary)
stringToNumber('z', 36); // 35 (letters are digits in high bases)
stringToNumber(''); // NaN (no digits at all)
+ or -, resolve the base, strip an optional 0x, then read digits.radix | 0. A value of 0 (or omitted) means base 10 with 0x inference; 16 also enables 0x stripping; any other value in 2..36 is used as-is; anything outside 2..36 returns NaN.a / A is 10 up to z / Z is 35, case-insensitive. A character counts only if its value is below the base.Number, you do not reject the whole string; you keep the valid prefix. stringToNumber('100 apples') is 100.NaN when nothing was read — an empty string, all whitespace, a lone sign, or a first character that is not a valid digit all yield NaN (not 0).atoi 32-bit clamp, decimal points, or exponents; those are notes in Going further.