pcjs/_posts/2015-03-26-javascript-idiosyncrasies.md
2016-11-21 08:51:17 -08:00

210 lines
7.3 KiB
Markdown

---
layout: post
title: JavaScript Idiosyncrasies
date: 2015-03-26 11:00:00
category: JavaScript
permalink: /blog/2015/03/26/
---
Time to mention a few JavaScript idiosyncrasies, and how I deal with them.
Also, see my previous posts on [PCjs Coding Conventions](/blog/2014/09/30/) and [JavaScript Negativity](/blog/2014/10/26/).
### Strict Equality
Many JavaScript websites will advise you to *never* use the "==" and "!=" JavaScript operators, because when they compare
variables containing different data types, JavaScript will coerce one of the operands to a matching type, sometimes in
unexpected ways. We can thank the early days of JavaScript for this feature, when it was trying to be extraordinarily
forgiving of sloppy code. I'm not going to list all the odd results that can arise from JavaScript's operand coercion,
because there are more than enough examples on the web already.
To avoid unexpected type coercion, and thus unexpected matches and/or mismatches, the usual advice is to *always* use
strict equality operators ("===" and "!==").
I disagree.
In well-written code, the variable data types should always be clear. In fact, the more you're able to
use [JSDoc](http://developers.google.com/closure/compiler/docs/js-for-compiler) types to declare the data types
of all your parameters, return values, and other variables, the fewer errors you'll have. As long as you're always
comparing variables with matching types, there shouldn't be any unexpected coercions.
Obviously, there will be times when a polymorphic variable is required, especially when dealing with APIs that can
return multiple types. But those should be the exception, not the rule.
Another exception is optional parameters. When I write a method with optional parameters, I generally allow those
parameters to either be omitted (ie, *undefined*) or set to *null*. Using "==", you can check for either value with
a single comparison:
``` javascript
if (parameter == null) { ... }
```
whereas strict equality requires more work:
``` javascript
if (parameter === undefined || parameter === null) { ... }
```
This is one of those times when coercion (of *undefined* to *null*), and the use of "non-strict" operators, is beneficial.
Here's another:
``` javascript
if (!b) { ... }
```
Coercing a value to *boolean* is a popular way of checking for all "falsy" values (ie, *undefined*, *null*,
0, false, "", NaN, etc). It is shorthand for:
``` javascript
if (b == false) { ... }
```
yet I suspect the proponents of strict equality would embrace the former while rejecting the latter.
However, I don't recommend "falsy" checks for optional parameters:
``` javascript
if (!parameter) { ... }
```
because often a valid numeric parameter might include 0, or a valid string parameter might include "", so it's better
to do this:
``` javascript
if (parameter == null) { ... }
```
and obviously if *null* is also a acceptable value, then you should definitely use strict equality:
``` javascript
if (parameter === undefined) { ... }
```
Problems with type coercion are **NOT** problems caused by a poor choice of operators, so trying to make
those problems go away by artificially limiting your choice of operators seems like the wrong solution.
Type coercion problems are, by definition, problems involving mismatched types. Solutions include:
- Avoid comparing variables of different types; or
- Convert your variables to matching types first; or
- Use strict equality operators (just don't use them mindlessly)
Explicitly convert variables to a single type whenever possible. For example, I might define a method
that accepts an optional numeric parameter, with a documented default value when it's omitted. I think it's
important make that parameter unambiguously numeric as soon as possible; eg:
``` javascript
/**
* foo(n)
*
* Performs a mathematical operation on n and returns a result.
*
* @param {number} [n] is an optional parameter (defaults to zero if omitted)
* @return {number}
*/
function foo(n) {
n = n || 0;
...
}
```
The expression `n || 0` might seem pointless, because *undefined* and *zero* are equivalent in a "falsy" sense, but
*undefined* is not a number, and there will be fewer problems downstream if you ensure that n is *always* a number.
### Enumerating Array or Object Properties
When using *for*...*in* loops like this:
``` javascript
var a = [100, 200, 300];
for (var i in a) { ... }
```
the type of variable *i* will be **string** rather than **number**; that is, it will contain "0", "1", and "2" rather
than 0, 1, and 2. If you then use *i* to set a matching element in another array, that element will not be stored in
the same (numeric) position as the original array.
One solution is to convert *i* to a **number**:
``` javascript
parseInt(i, 10);
```
However, a more elegant solution is to use the unary "+" operator to coerce the **string** to a **number**:
``` javascript
+i;
```
The same problem arises with objects using numeric properties. And watch out for JavaScript's automatic base
conversion of numeric properties. For example, when you enumerate the properties of object "o":
``` javascript
var o = {
0x20: ' ',
0x41: 'A'
};
```
you will get the strings "32" and "65", not "0x20" and "0x41". You must quote your property names to prevent
any conversion; eg:
``` javascript
var o = {
"0x20": ' ',
"0x41": 'A'
};
```
Numeric properties can always be safely converted using the unary "+" operator, regardless whether they were quoted
or not.
The unary "+" is a great alternative to parseInt(), but be mindful of their differences. One important difference
is that parseInt() will stop when it encounters an invalid digit, returning whatever value was parsed up to that point,
whereas unary "+" conversion will return *NaN* if there are any invalid digits in the string.
### Shift Counts For Bitwise Shifts
It turns out that shifting an integer value by more than 31 bits in either direction may not shift as many bits as
you'd expect. For example:
``` javascript
n = 0x10000000;
n >>>= 33;
```
will shift n by only *one* bit, not 33 bits, and the result will be 0x08000000, not zero. This is because,
just like the shift instructions on 32-bit Intel processors, JavaScript converts the shift count to a *mod 32* value
(in other words, it truncates the shift count to a 5-bit value).
So the above example is equivalent to:
``` javascript
n >>>= 1;
```
If you really need larger shift counts to work in a consistent manner, you can perform multiple shifts, where each
shift count is in the range 0-31. Here's one way to shift a number 33 bits:
``` javascript
n = (n >>> 31) >>> 2;
```
Also, it's not quite correct to say that a shift count of zero has *no* effect on a number:
``` javascript
n = 0x88888888|0; // n is displayed as -2004318072
n >>>= 0; // n is displayed as 2290649224
```
It's true that the bottom 32 bits of the number were not changed, but a side-effect of the unsigned shift operator
is that all the upper sign bits are stripped from the (64-bit) result.
Similarly, as soon as you perform any other bitwise operation on the number, even one that does not modify the low
32 bits, the upper bits will revert to the sign of the lower 32-bit value:
``` javascript
n |= 0; // n is displayed as -2004318072 again
```
*[@jeffpar](http://twitter.com/jeffpar)*
*March 26, 2015*