Javascript gotchas thread



  • Was about to post this in Status, but fuck that, this needs to be documented.

    Here's what just bit me in the ass. A property that was supposed to be true/false value had this huge ass object instead. The thing sort of worked because the object acted as "truthy" value, so it was only through accident that I even noticed it. Traced it down to a code like this:

    function isBoth(a, b) {
        return a && b;
    }
    

    Hmm ok, so let's test this shit

    console.log(isBoth(false, { wat: "some hash" }));
    console.log(isBoth(true, { wat: "some hash" }));
    

    Expected output:

    false
    true
    

    Actual output:

    false
    { wat: "some hash" }
    

    WTF javasript? Y u hate me!?



  • JS is more evil than PHP.



  • You can use foo || bar to make default values for variables.



  • @ben_lubar said:

    You can use foo || bar to make default values for variables.

    Not the point. I just did !!a && !!b or something. The point is, this kind of shit comes out of nowhere and is totally unpredictable.



  • In JavaScript, x && y means "If x is truthy, x. Otherwise, y." It's actually more common in my experience for languages to do that than to coerce values (C is the only one I know of that does that.)



  • @cartman82 said:

    The point is, this kind of shit comes out of nowhere and is totally unpredictable.

    Just a part of JS's general "whatever shit you throw at the interpreter, it will do something with it" philosophy.

    Can't anyone fork JS into a language that actually complains when you do something that makes no sense, instead of just rolling with it?

    @ben_lubar said:

    In JavaScript, x && y means "If x is truthy, x. Otherwise, y."

    And it makes zero sense, because not only is an AND operator supposed to return a, well, logical value - but there's already a common, just as short and less confusing syntax in x ? x : y.



  • @Maciejasjmj said:

    x ? x : y

    That evaluates x twice if it's truthy.



  • @ben_lubar said:

    In JavaScript, x && y means "If x is truthy, x. Otherwise, y."

    Actually, wait a second. Shouldn't that be x || y? If x is truthy, x && y doesn't short-circuit.


    Filed under: while we're being pedantic dickweeds



  • Goddamn boolean algebra


  • Discourse touched me in a no-no place

    @ben_lubar said:

    truthy.

    Truthiness is as stupid in computing as it is when Dan Rather tried to throw the 2004 election with it.





  • @ben_lubar said:

    In JavaScript, x && y means "If x is truthy, x. Otherwise, y." It's actually more common in my experience for languages to do that than to coerce values (C is the only one I know of that does that.)

    I always mentally treat && as a boolean operator that does comparison and returns true or false. I can see your interpretation is correct. Doesn't mean I like it. What other language are doing this? (so I know to steer clear of them :-))



  • @cartman82 said:

    I can see your interpretation is correct.

    We already established that it isn't.



  • Right. That's ||.
    && is then "if a is falsy, then a. Otherwise b".

    function both(a, b) {
        return a && b;
    }
    
    console.log(both(0, {wat: "hash"}));
    console.log(both(1, {wat: "hash"}));
    
    // 0
    // { wat: 'hash' }
    

    Fucking boolean algebra. And javascript.



  • x && y should just be x & (&y). That'd save everyone some confusion.



  • The same feature is in Lua. Although, it uses and for && and or for ||. This makes code much more fun to read:

    x = a or b;
    

    Wait, I have to pick? I want both!

    x = a and b;
    

    Great!



  • @ben_lubar said:

    x && y should just be x & (&y). That'd save everyone some confusion.

    That's not valid code.

    @cvi said:

    The same feature is in Lua. Although, it uses and for && and or for ||. This makes code much more fun to read:

    x = a or b;

    Wait, I have to pick? I want both!

    x = a and b;

    Great!

    PROCEDURE Test;
    VAR x: Integer;
        y: integer;
    BEGIN
        IF x = True AND y = True THEN
            WRITELN('Both true');
        ELSE
            WRITELN('Not');
        END;
    END;
    


  • I find it interesting to note that PHP supports and and or in comparison to && and || but at lower precedence to them. && and || have a bunch of lower precedence operators then and and or.



  • @cartman82 said:

    That's not valid code.

    xy.c:3:19: error: invalid operands to binary & (have ‘int’ and ‘int *’)
      printf("%d\n", x & (&y));
    

    Ok, so C is slightly less bad than I remember.



  • @Arantor said:

    I find it interesting to note that PHP supports and and or in comparison to && and || but at lower precedence to them. && and || have a bunch of lower precedence operators then and and or.

    C++ also allows the alternate "and/or" spellings (apparently - don't think I've ever used them). Additionally there's <ciso646>, just to make sure.

    At least and really is equivalent to &&... Then again, different precedence would probably enable awesome amounts of creative abuse <3.

    @ben_lubar said:

    Ok, so C is slightly less bad than I remember.

    Hey, in C++ this is fixable! Just override a few operators.



  • It certainly makes for interesting debugging if one is not familiar with the fact they have a different precedence.


  • Considered Harmful

    @Maciejasjmj said:

    Can't anyone fork JS into a language that actually complains when you do something that makes no sense, instead of just rolling with it?


  • :belt_onion:

    @ben_lubar said:

    In JavaScript, x && y means "If x is truthy, x. Otherwise, y." It's actually more common in my experience for languages to do that than to coerce values (C is the only one I know of that does that.)

    Isn't that, "If x is truthy then y, if x is... falsy?... then x"


  • :belt_onion:

    @cartman82 said:

    && is then "if a is falsy, then a. Otherwise b".

    [code]
    function isBoth(a, b) {
    return a && b;
    }
    isBoth(0, 1);
    0
    isBoth(2, 1);
    1
    [/code]
    so I'd say the problem is in the function name :trollface: that doesn't sound at all like what it returns!



  • @error said:

    http://www.typescriptlang.org

    But how will idiots highly skilled offshore developers write JS now?!



  • Faster and better, with a 5% increase in frustration until the type system saves them from a serious problem.



  • @Arantor said:

    && and || have a bunch of lower precedence operators then and and or

    The main thread and this are all ideas borrowed from sh. They make a lot of sense in a shell, where you can run things like "command a && command b" and "if command a then command b else command c or die".

    They make so sense at all in a proper programming language, with sane error handling options. But ok, we are talking about Javascript and PHP...



  • That's how every other language I've used works. Well, except Java and C. But Ruby, Python, Perl, Awk, Bash, Lisp, Scheme, and not mentioning C++ because it's in a class of its own..



  • @Mo6eB said:

    Ruby, Python, Perl, Awk, Bash, Lisp, Scheme

    Out of these, I have significant experience only with bash. And I know that && and || operators control the flow based solely on return codes. You will not get some object or array stuck in memory based on a comparison one-liner. Which is the whole point here.



  • The expectation with && in the given code is that it implicitly converts a truthy value into a boolean value.

    But it does not. Found this on developer.mozilla.org: "expr1 && expr2 (Logical AND) returns expr1 if it can be converted to false; otherwise, returns expr2."

    The key here is "expression" - and in a way that leads to the real wtf: if you mix types in a logical expression (bool with hash in this case) you should read the documentation and not just blindly trust JS to do the right thing. Because it does - for its own interpretation of "right".



  • BTW, Perl shows exactly the same behaviour:

    sub comp {
        my $i = 42;
        my $h = { 'foo' => 'bar' };
        return $i && $h;
    }
    
    my $res = comp();
    print "res='$res'\n\n";
    exit;
    

    Gives me:

    [foo@bar test]$ ./test.pl
    res='HASH(0x829a8c4)'
    


  • Yup. A subtle gotcha of loosely typed languages in general. I'm certainly keeping this in mind from now on.



  • @cartman82 said:

    function isBoth(a, b) {
    return a && b;
    }

    This is called short circuit evaluation and is, why you may do things like this:

    var requestAF = window.requestAnimationFrame
                    || window.webkitRequestAnimationFrame
                    || window.mozRequestAnimationFrame;
    if (requestAF) requestAF( function() { /*...*/ } );
    

    If you want a boolean value and a boolean value only, cast to Boolean:

    var condition = Boolean(a && b);
    

    How short circuit evaluation works:
    a) AND: return false for first value that doesn't evaluate to true, since the entire expression will evaluate to false for this. Else, return the last value evaluated.
    b) OR: return first value that evaluates to true, since the whole expression will be true as long as any element evaluates to true. Else, return false.
    Thus AND and OR are just some kind of elaborate if-else-clause.

    In action:

    function f(x) {
       console.log(x);
       return x;
    }
    
    var c1 = f(1) && f(2) && f(3);
    // logs 1 2 3
    // c1 === 3
    
    var c2 = f(1) || f(2) || f(3);
    // logs 1  (2 and 3 never called)
    // c2 === 1
    

    Or, as JS code for evaluating AND and OR:

    function and() {
       var ret = undefined;
       for (var i = 0; i < arguments.length; i++) {
          if (!arguments[i]) return false;
          ret = arguments[i];
       }
       return ret;
    }
    
    function or() {
       for (var i = 0; i < arguments.length; i++) {
          if (arguments[i]) return arguments[i];
       }
       return false;
    }
    

    In other words, OR checks for the first true value and returns it, else returns false. AND checks for the first false value or returns the last value evaluated. For this you may use AND and OR as a filter.



  • @cartman82 said:

    Yup. A subtle gotcha of loosely typed languages in general. I'm certainly keeping this in mind from now on.

    I regularly use the pattern return (something_truthy) ? true : false; (or return (something_truthy) ? 1 : 0; in Perl).

    Looks like unnecessary redundancy but it communicates the type of the return value rather well.



  • I think I'll go with

    !!(truthy_expression)
    

    BTW, why is this blue? Start code line with !.


  • Discourse touched me in a no-no place

    test...

    Guess

    !!(truthy_expression)
    

    Bash

    !!(truthy_expression)
    

    Sql

    !!(truthy_expression)
    

    Diff

    !!(truthy_expression)
    

  • 🚽 Regular

    ++test;
    
    --test;
    

    Diff?


  • Discourse touched me in a no-no place

    @Zecc said:

    Diff?

    That one looks like it. If you don't hint the block it tries guessing based on what's there.


  • 🚽 Regular

    I'm suggesting the reason

    ! whatever
    

    is blue is because it is being interpreted as a diff where ! marks a line with a change.


  • Discourse touched me in a no-no place

    @Zecc said:

    where ! marks a line with a change.

    Seems to be the case (see my edited post) but I can't recall ever seeing that on the commandline...

    Off to Google I go...



  • I appreciate the irony of posting a WTF about the only thing which makes sense in JS.

    Gary Bernhardt already documented a number of things in his 2012 lightning talk titled "Wat", but the video seems to have gone missing. Here's the abridged version:

    > [] + [] // Array plus array
    "" // Evaluates to empty string

    > [] + {} // Array plus object
    [object Object] // Is object

    > {} + [] // And likewise, the reverse
    0 // Equals zero

    > {} + {} // Object plus object
    NaN // Not a Number, obviously

    I can personally add the following:

    > (function () { return this; }).call(null) // Set a property on a null object?
    Window { ... } // Whoops, it's a global

    > var undefined = 12 // Set a variable that happens to be a reserved word?
    12 // Error? What error?

    > {} // Sanity check
    undefined // Congratulations, you're insane


  • Discourse touched me in a no-no place

    @PJH said:

    Off to Google I go...

    And it's because I generally use unified rather than context.



  • Yeah I know about the "Wat" video. But that's just this hipster finding ways to intentionally break javascript. This one actually bit me in the ass in RL. I wasn't trying to do stupid shit. For some reason, I just didn't expect && to act that way, even though I'm using || to select fallbacks all the time.



  • JavaScript is dangerous without some sort of linter or layer on top of it. The language has good parts, but it's too easy to accidentally step into the bear traps it has laying all over the place.

    People who say "don't worry, I know that language" and work without a net will get bit by this over and over again. It's not the code you wrote on purpose that gets you, it's the code you wrote by accident that "almost works" that will cause you days of tracking down a bug.


  • 🚽 Regular

    @Faxmachinen said:

    > var undefined = 12 // Set a variable that happens to be a reserved word?12 // Error? What error?
    "undefined" didn't use to be a reserved word. People used it as a keyword, but it was really just an undeclared variable which, by default (like any other undeclared variable), had a value of type "undefined".

    Now that it is a reserved word, not getting an error is a WTF. But you're not actually redefining undefined, the value 12 you're getting is the value returned by the assignment.

    ▶ undefined = 12
    ◂ 12
    ▶ undefined
    ◂ undefined
    

    Same thing happens if you do this:

    ▶ obj = {}
    ◂ Object {}
    ▶ Object.defineProperties(obj, { 'prop': {value: "Hello", writable: false} })
    ◂ Object {prop: "Hello"}
    ▶ obj.prop = 7
    ◂ 7
    ▶ obj.prop
    ◂ "Hello"
    

    See? On the one hand, Chrome/Firefox/NodeJS are completely silent about the ignored assigment; on the other hand, Discourse broke another quote [EDIT] in the preview, not on the baked post 😒


  • ♿ (Parody)

    @cartman82 said:

    Was about to post this in Status, but fuck that, this needs to be documented.

    The js stuff is definitely sidebar worthy, but my favorite thing about this thread is the concept of the Side Bar being documentation.



  • console.log(isBoth(TypeInference, Logic));



  • @cartman82 said:

    I wasn't trying to do stupid shit. For some reason, I just didn't expect && to act that way, even though I'm using || to select fallbacks all the time.

    Okay, here's one that actually got me:
    How does JavaScript interpret the expression a << b == c & d?

    1. (a << b) == (c & d)
    2. a << (b == (c & d))
    3. ((a << b) == c) & d

    Hint: JavaScript got its operator precedence from C. And C didn't always have the && and || operators.


  • :belt_onion:

    @Faxmachinen said:

    How does JavaScript interpret the expression a << b == c & d?

    That reminds me of this question - which ones of these if blocks will or will not print, given the following initializations to the variables? Do you think it will work the same if alert is changed to echo and it's run in PHP instead of JS?

    [code]
    $a = 1;$b = 2;
    [/code]

    [code]
    if( ! $a == $b ){
    alert(1);
    }
    [/code]

    [code]
    if( ! ($a == $b) ){
    alert(2);
    }
    [/code]

    [code]
    if( $a != $b ){
    alert(3);
    }
    [/code]

    [code]
    if( (!$a) == $b ){
    alert(4);
    }
    [/code]
    wee



  • @darkmatter said:

    That reminds me of this question - which ones of these if blocks will or will not print, given the following initializations to the variables? Do you think it will work the same if <code>alert</code> is changed to <code>echo</code> and it's run in PHP instead of JS?

    [code]
    $a = 1;$b = 2;
    [/code]

    [code]
    if( ! $a == $b ){
    alert(1);
    }
    [/code]

    [code]
    if( ! ($a == $b) ){
    alert(1);
    }
    [/code]

    [code]
    if( $a != $b ){
    alert(1);
    }
    [/code]

    [code]
    if( (!$a) == $b ){
    alert(1);
    }
    [/code]
    wee

    None of them print.


Log in to reply