42

I was reviewing the code for an angularjs factory to better understand how it works. The code contains an if statement that I don't fully understand.

In a plnkr demo the author wrote this:

if ((+!!config.template) + (+!!config.templateUrl) !== 1) {
  throw new Error('Expected modal to have exactly one of either `template` or `templateUrl`');
}

It is slightly different in the github repo:

if (!(!config.template ^ !config.templateUrl)) {
  throw new Error('Expected modal to have exactly one of either `template` or `templateUrl`');
}

Obviously by the error message it is checking to see if one of the two exists. I'm just not sure how it comes to the conclusion. I have not been able to find any information on ^ or +!

My question is: How does this if statement work? (^ or +! or +!! specifically)

Matsemann
  • 21,083
  • 19
  • 56
  • 89
Jack
  • 2,741
  • 2
  • 24
  • 32
  • 8
    ^ is the javascript bitwise XOR operator. – Jacques ジャック Dec 28 '15 at 22:23
  • `I have not been able to find any information on ^ or +!` you didn't use the right keyword. search for `javascript operator` – phuclv Dec 29 '15 at 03:00
  • First ! takes the value - like config.template (which is probably a number because of the +!!), converts it to true or false based on whether it is zero (false) or non-zero (true) - which inverts the logical sense - then the second ! inverts it back to the same logical sense instead of being inverted, then the + treats true/false as the number 1 or 0. So if it's not set, +!! will result in the *number* 0, otherwise it will be the *number* 1 *regardless of what the actual number value of .template is*. Then they can do the + between the two expressions. – simpleuser Dec 29 '15 at 08:43
  • 2
    Hopefully this is the result of putting simple code through a code obfuscator, because there is no possible way a human would ever write code like this. Yeah, right. – Mohair Dec 29 '15 at 15:45
  • 1
    Is it just me or are "what is this mysterious operator (which is actually just 2+ operators stuck together without spaces)" questions appear more often lately? – Oleg V. Volkov Dec 29 '15 at 16:27
  • 1
    `!!` = two logical-not operators; `+` = unary plus: http://xkr.us/articles/javascript/unary-add/; the combination can be used for truthy/falsy conditions to integers. – vol7ron Dec 29 '15 at 16:35
  • [Related discussion about the operator in `perl`](http://stackoverflow.com/questions/33014080/why-is-considered-bad-form-in-perl/33014166#33014166) (in case it's of interest) – Sobrique Dec 29 '15 at 16:55
  • @OlegV.Volkov [didn't you get the memo](http://meta.stackoverflow.com/q/313593/792066) – Braiam Dec 30 '15 at 07:38
  • @Mohair it's not obfuscated code. In C `!!` is very commonly used to coerce a value to 0 or 1. In Javascript you must use `+!!` – phuclv Dec 30 '15 at 08:03

6 Answers6

72

!! converts a value to a boolean (true or false). + then converts that boolean to a number, either 1 for true or 0 for false.

> +true
1
> +false
0

Personally I find it clearer to write something like this, when dealing with two booleans:

if (!config.template == !config.templateUrl) {
  throw ...
}

Code clarity and readability be damned, apparently.

Community
  • 1
  • 1
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • 4
    Please note that if those two are indeed booleans then you can also write `if (config.template == config.templateUrl) {}` ... however one or both of these is probably a string with the other being an `undefined` or `null`, which is why the `!` operators make a difference. It would also be nice to just insert the extra lines to catch both errors separately; `if (template && templateUrl) throw new Error("Saw both template and templateUrl; which do you actually want to use?"); if (!template && !templateUrl) throw new Error("Saw neither a template nor a templateURL, I have nothing to render!");` – CR Drost Dec 29 '15 at 15:16
  • 3
    I don't personally like `+!!` but it's worth noting that it would scale to "expected exactly 1 of {3 things} to be specified" more cleanly. In my opinion, most readable would be something like `if (truthyCount(item1, item2, item3) !== 1)` – default.kramer Dec 29 '15 at 17:59
  • @torazaburo I'm pretty sure it's correct... there's an outer negation around the XOR. Did I brain fart this one? – Matt Ball Dec 30 '15 at 01:55
  • @torazaburo I can show my work: for booleans `A` and `B`, `(A XOR B)` is the same as `(A != B)`. Therefore `!(A XOR B)` is equivalent to `!(A != B)` or more simply, `A == B`. – Matt Ball Dec 30 '15 at 01:57
  • Personally, I think the exclusive or version (from the OP) is the most readable. It makes it clear that you want one or the other to be true. Your code takes a lot more thinking to realize that. Like, way more. Sure, `!!` is a bit ugly, but it shouldn't be hard for a JS developer to figure out what it does. I'd argue that every JS programmer should be able to figure it out by some point of time. The idea of JS doing type conversions with certain operations is instilled pretty darn early in learning JS (and fundamentally important for debugging). – Kat Jan 04 '16 at 22:42
34

+!! uses implicit conversion to cast a value as a 0 or 1 depending on its boolean value

For the most part, this is to check for existence. For example, an empty string is false (!!"" === false), and so is undefined, and a number of others. Those are the main two though

"Falsey" conversions

+!!""        ===   0
+!!false     ===   0
+!!0         ===   0
+!!undefined ===   0
+!!null      ===   0
+!!NaN       ===   0

"Truthy" conversions

+!!1         ===   1
+!!true      ===   1
+!!"Foo"     ===   1
+!!3.14      ===   1
+!![]        ===   1
+!!{}        ===   1

if ((+!!config.template) + (+!!config.templateUrl) !== 1)

Hopefully this is making more sense at this point. The object config has two properties we are examining. .template and .templateUrl. The implicit cast to a 0 or 1 using +!! is going to be added and then compared to ensure that it is not 1 (which means it is either 0 or 2) - the properties can either both be on or off but not different.

The truth table here is as follows:

template    templateUrl    (+!!) + (+!!)     !==1
"foo"       "foo"              1 + 1         true
undefined   undefined          0 + 0         true
undefined   ""                 0 + 0         true
""          undefined          0 + 0         true
12          ""                 1 + 0         false
""          12                 0 + 1         false
undefined   "foo"              0 + 1         false
""          "foo"              0 + 1         false
"foo"       ""                 1 + 0         false
"foo"       undefined          1 + 0         false

A much simpler method to all of this would have been to just use the implicit boolean conversion

if (!config.template === !config.templateUrl)
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • 1
    Your simpler method is less code, yes, but I'd argue that it's less readable for anyone who has not seen that pattern before (logical xor doesn't pop up often). Your version requires some thinking: "the negation of this to be equal to the negation of that" compared to "not this xor not this" (the negation in OP's if statement code, of course, is because they're checking if this condition failed). I'd personally break this into two lines, with `var hasValidTemplate = !!config.template ^ config.templateUrl; if(!hasValidTemplate)`. – Kat Jan 04 '16 at 22:48
  • [continued - the previous comment also forgot the `!!` before `config.templateUrl`] The `!!` coercion to boolean is sadly necessary in the above code is sadly necessary due to the fact that `^` is bitwise xor and we want logical xor, for which we must convert to booleans. An alternative for readability could be `var hasTemplate = !!config.template; var hasTemplateUrl = !!config.templateUrl; if(!(hasTemplate ^ hasTemplateUrl)`. – Kat Jan 04 '16 at 22:53
31

This is a horribly unreadable way to write out the boolean value of a variable, and then convert it using unary conversion to give a 0/1 number result.

Consider:

+!!true; //this converts true to false, then to true again, and true is 1 when converted
+!!0; //converts 0 (falsy) to true, then false, and then the numeric 0

Technically speaking !! is not its own operator, it's just the NOT (!) operator twice.

Unary conversion: ECMA spec doc a unary plus attempts to convert to an integer. Number() would also be a valid conversion.

kyle
  • 691
  • 1
  • 7
  • 17
Sterling Archer
  • 22,070
  • 18
  • 81
  • 118
  • `Number` is a suitable replacement here, but `parseInt` wouldn't be useful for converting a *boolean* to a number, though, right? – apsillers Dec 28 '15 at 23:16
  • 3
    I've often heard the justification that "This code is so complicated/intricate/important/whatever that if you don't know how to parse these operators like the back of your hand, you shouldn't be messing with this code anyway." but honestly all I hear is "Let's make this incredibly complicated code even harder to parse!" – corsiKa Dec 28 '15 at 23:51
  • 3
    Incorrect. `parseInt(!!x)` is not the same as `+!!x`. The former is `NaN`. Correctness aside, I'm not sure what the *horrible readability* rationale is. `+!!x` is succient and unambiguous. (Though the expression as a whole can be simplified as Matt points out.) – Paul Draper Dec 29 '15 at 16:06
6

Meanwhile, ^ is the bitwise XOR operator.

When dealing with numbers smaller than 2, ^ will work like a boolean OR (||) if you consider 0 = false and 1 = true.

jrharshath
  • 25,975
  • 33
  • 97
  • 127
  • Side note, its comparatively slow in javascript due to the need to convert the operands to 32 bit integers and then back to 64 bit floats. – Jared Smith Dec 29 '15 at 18:53
6

! is the logical not operator. It is a unary operator that converts its operand to a boolean and then negates it. !! is just that operator twice, the second ! undoes the negation, so the end result is just conversion to a boolean.

+ is the unary plus operator, which converts it's operand to a number. In the case of a boolean, false becomes 0, and true becomes 1.

So, +!!(expression) evaluates to 1 if the expression is truthy and 0 if the expression is falsy.

Paul
  • 139,544
  • 27
  • 275
  • 264
3
if ((+!!config.template) + (+!!config.templateUrl) !== 1) {

            0            +         0               !== 1  true
            0            +         1               !== 1  false
            1            +         0               !== 1  false
            1            +         1               !== 1  true

is equal to

if (!config.template === !config.templateUrl) {

despite the content of the two properties.

Nina Scholz
  • 376,160
  • 25
  • 347
  • 392