niedziela, 30 czerwca 2019

JavaScript built-in functionality - be careful and read docs

JavaScript has a lot of very useful built-in functions, like isNaN(), parseInt(), isFinite() and so on. Those functions are simple and single purpose pieces of code used very often.

The isNaN() function takes only one argument - the value which should be checked and returns a boolean - true or false. The only purpose is to check if the value is NaN - not a number value which could be returned by functions parsing numbers or by arithmetical operators. There is no other way to check if the value is NaN, so this function is very important.

There is also other 'is' function - isFinite() which determines whether the passed value is a finite number. Also very important as some of the arithmetic operators can return Infinity as a result of the operation and then isNaN() returns false. It also takes only one argument - the value to be checked and returns boolean.

Another useful single purpose function is parseInt(). It checks provided string if it could be the number and returns the number or NaN.

As there are more than one numeral systems, this function accepts the radix as a second argument. There is NO DEFAULT value for radix. If radix is falsy then the value itself is taken in consideration. Because we can use Number(string) to determine whether the string is a number or not parseInt() works in a slighty different way. In general it checks if the string starts with the number and returns this number till first non-numerical value.

parseInt('1', 10); // 1
parseInt('1abc', 10); // 1
parseInt('1f0', 10); // 1

parseInt('1f0', 16); // 496

The parseFloat() built-in function works the same way, except it accepts only one argument - the value, as floats are decimals, so the numeral system is already known.

Both parseX functions are documented that the value should be a string but any value type works properly.


Array.prototype.map is a built-in Array method. It takes 2 arguments: a callback function and this. Returns the brand new array with the same number of elements as the array it was called upon with values produced by the callback function. Simple and beautiful.

It is very important to remember that callback function also accepts arguments. All of them are optional, as the logic for creating values for the new array could not depend on existing ones, but still the callback accepts arguments. The first one is a current value. For [1,7,11] it will be 1, then 7 and then 11. The second argument is index of the value - for 1 it will be 0, for 7 - 1 and for 11 - 2. The third argument is the array map was called upon.

[1,7,11].map(() => null); // [null, null, null]
[1,7,11].map((value) => value * 2); // [2, 14, 22]
[1,7,11].map((value, index) => value * index); // [0, 7, 22]

So what will happen if we want to use parseFloat() as a callback for map?

['1','7','11'].map(parseFloat); // [1, 7, 11]
['1.1','7.9876','11.444442'].map(parseFloat); // [1.1, 7.9876, 11.444442]

Works like a charm!

So why parseInt() doesn't work as expected?

['1','7','11'].map(parseInt); // [1, NaN, 3]

What is a problem here? Let's test the values:

parseInt('1'); // 1
parseInt('7'); // 7
parseInt('11'); // 11

It should work...

Everytime if something doesn't work as expected checking docs can save hours of tests. The second argument in parseInt() is radix and what passes map() as a second parameter to the callback? An index of the value...

So testing the values should look like that:

parseInt('1', 0); // 1

For index 0 value is '1'. The radix is falsy, so the value itself is taken in consideration. If value doesn't start with 0 decimal system is used.

parseInt('7', 1); // NaN

For index 1 value is '7'. Proper radix value is between 2 and 36. Any value parsed with 1 as a radix is NaN.

parseInt('11', 2); // 3

For index 2 value is '11'. Radix 2 means binary: 2^0 + 2^1 = 3.

So everything works just as it should. To make the callback work as expected a small change is required:

['1','7','11'].map((value) => parseInt(value, 10)); // [1, 7, 11]

It is not the newbie mistake. Such problems happen for seasoned developers, as they are not machines and don't store not used information like the order or all arguments for built-in functions. Using documentation during development process doesn't mean developer has no experience or knowledge. Using docs is a very good habit and the best possible practice which saves hours of bug fixing.

All recruitment processes which check the knowledge of docs are leading to such problems. General understanding of the language, what happens under the hood and problem solving skills and strategies (for example how to debug such bug) are more important than memorizing the documentation.

Good to read:
operacje arytmetyczne w JS [polish]
parseInt() - JavaScript | MDN
Array.prototype.map() - JavaScript | MDN