JavaScript

Horror Show

(things your mother didn't dare to tell you)

By Enrique Amodeo / @eamodeorubio

Enrique Amodeo

(who is this guy?)

[Enrique Amodeo, circa 2010]
  • Working as a professional since 2000
  • Worked with JAVA/JEE and big companies
  • Has loved JS since 2005
  • Test infected
  • Enthusiast of the Agile/Lean way
  • Now Freelancing
  • Follow me at @eamodeorubio

Be warned !

(explicit JS inside this talk!)

This talk

  1. Type coercion hell
  2. Scary operators
  3. Invasion of the Body Snatchers
  4. Functional gore

Type coercion hell

(burn developer, burn!)

Original by Dave Hogg: http://bit.ly/ZFFbdk

The types in JS

(it's easy)

  • undefined
  • boolean*
  • number*
  • string*
  • object
  • function

*These can have objects & primitive values

Type conversions

(looks harmful)

  • Boolean(expr)
  • [[ToPrimitive(expr, hint)]]
  • Number(expr)
  • String(expr)
  • There are more...

Automatic Type conversion

(the JS interpreter is a smart ass)

What conversion will be used?

Ehem, it depends on...

  • What type is the interpreter expecting
  • The operator in the expression
  • The position of the Moon in the Sky...

Truthy & Falsy


    function sayHi(x) {
      if (!x)
        return console.log('Bye');
      console.log('Hi');
    }
    //What will this print?
    sayHi(false);
    sayHi(null);
    sayHi([]);
    sayHi(new Boolean(false));
  

Truthy & Falsy


    function sayHi(x) {
      if (!x)
        return console.log('Bye');
      console.log('Hi');
    }
    //What will this print?
    sayHi(false); // #=> 'Bye'
    sayHi(null); // #=> 'Bye'
    sayHi([]); // #=> 'Hi'
    sayHi(new Boolean(false)); // #=> 'Hi'
  

Boolean(expr)

(falsy values)

Everything is converted to true, except...

  • false
  • null
  • undefined
  • NaN
  • 0
  • ""

What about new Boolean(false) ?

typeof expr

(which is the type of expr?)

  • typeof false -> 'boolean'
  • typeof 'false' -> 'string'
  • typeof new Object() -> 'object'
  • typeof new Boolean(false) -> 'object'
  • Boolean(new Boolean(false)) -> true

Number(expr)

(basic cases)

  1. undefined -> NaN
  2. null -> 0
  3. false -> 0, true -> 1
  4. "" -> 0
  5. "23.23e+2" -> 2323
  6. "23.23sss" -> NaN

String(expr)

(basic cases)

  1. undefined -> 'undefined'
  2. null -> 'null'
  3. false -> 'false', true -> 'true'
  4. 23.23e+2 -> '2323'
  5. 222e22 -> '2.22e+24'

Number(expr) & String(expr)

(if expr is an object)

  1. Number(obj) ->

    Number(ToPrimitive(obj,'number'))

  2. String(obj) ->

    String(ToPrimitive(obj,'string'))

ToPrimitive?? What is that?

ToPrimitive(expr, hint)

(hint is 'number')

  1. Has a valueOf() method?

    return obj.valueOf()*

  2. Otherwise

    return obj.toString()*

*Fail if toString or valueOf returns a non primitive

ToPrimitive(expr, hint)

(hint is 'string')

  1. Has a toString() method?

    return obj.toString()*

  2. Otherwise

    return obj.valueOf()*

*Fail if toString or valueOf returns a non primitive

ToPrimitive(expr, hint)

(no hint)

  1. Is expr a Date object?

    return ToPrimitive(expr, 'string')

  2. Otherwise

    return ToPrimitive(expr, 'number')

Automatic fancy conversions

(know what the operators expect)

  1. if(expr) -> if(Boolean(expr))
  2. a - b -> Number(a) - Number(b)
  3. a * b -> Number(a) * Number(b)
  4. a / b -> Number(a) / Number(b)
  5. a && b -> Boolean(a) && Boolean(b)*
  6. a || b -> Boolean(a) || Boolean(b)*
  7. ! a -> ! Boolean(a)

*Not exactly...

Scary operators

(run for your lives!)

Original by Jimmy Bramlett: http://bit.ly/Xop0TG

What do you expect?

(the + operator)

    var a = {
      valueOf:function() {
        console.log("valueOf a");
        return "1";
      }
    };
    var b = {
      valueOf:function() {
        console.log("valueOf b");
        return "2";
      }
    };
    console.log(a + b);
    console.log(b + a);
    console.log(a - b);
    console.log(b - a);
    console.log(+a);

What do you expect?

(the + operator)

    var a = {
      valueOf:function() {
        console.log("valueOf a");
        return "1";
      }
    };
    var b = {
      valueOf:function() {
        console.log("valueOf b");
        return "2";
      }
    };
    console.log(a + b); // #=> '12'
    console.log(b + a); // #=> '21'
    console.log(a - b); // #=> -1
    console.log(b - a); // #=> 1
    console.log(+a); // #=> 1

The + operator

(your best friend)

  1. (unary) +a -> Number(a)
  2. a + "hola" -> String(a) + "hola"
  3. "hola" + a -> "hola" + String(b)
  4. a + 6 -> Number(a) + 6
  5. 6 + a -> 6 + Number(b)
  6. a + b

    -> ToPrimitive(a) + ToPrimitive(b)

Comparision operators

(almost like +)

  1. 1 < 2 -> true
  2. "ab" < "aa" -> true (lexical order)
  3. a < 6 -> Number(a) < 6
  4. 6 < a -> 6 < Number(b)
  5. a < b

    -> ToPrimitive(a) < ToPrimitive(b)

  6. <=, >, >= defined in terms of <

What do you expect?

(some classics)

    var a = {
      valueOf: function () {
        return "2";
      }
    };
    console.log(1 == '1');
    console.log(1 == true);
    console.log(0 == '');
    console.log(null == null);
    console.log(null == undefined);
    console.log(null == '');
    console.log(null == 0);
    console.log(NaN == NaN);
    console.log(a == 2);
    console.log(a == true);
    console.log(a == false);

What do you expect?

(some classics)

    var a = {
      valueOf: function () {
        return "2";
      }
    };
    console.log(1 == '1'); // #=> true
    console.log(1 == true); // #=> true
    console.log(0 == ''); // #=> true
    console.log(null == null); // #=> true
    console.log(null == undefined); // #=> true
    console.log(null == ''); // #=> false
    console.log(null == 0); // #=> false
    console.log(NaN == NaN); // #=> false
    console.log(a == 2); // #=> true
    console.log(a == true); // #=> false
    console.log(a == false); // #=> false

The == operator

(love it!)

  1. null == undefined
  2. undefined == null
  3. NaN == x is always false
  4. 15.2 == 15.2
  5. 'juan' == 'juan'
  6. primA == primB ->

    Number(primA) == Number(primB)

  7. ob1 == ob2 ->

    ToPrimitive(ob1)==ToPrimitive(ob2)

The === operator

(use almost always)

    var a = {
      valueOf: function () {
        return "2";
      }
    };
    console.log(1 === '1');
    console.log(1 === true);
    console.log(0 === '');
    console.log(null === null);
    console.log(null === undefined);
    console.log(null === '');
    console.log(null === 0);
    console.log(NaN === NaN);
    console.log(a === 2);
    console.log(a === true);
    console.log(a === false);

The === operator

(use almost always)

    var a = {
      valueOf: function () {
        return "2";
      }
    };
    console.log(1 === '1'); // #=> false
    console.log(1 === true); // #=> false
    console.log(0 === ''); // #=> false
    console.log(null === null); // #=> true
    console.log(null === undefined); // #=> false
    console.log(null === ''); // #=> false
    console.log(null === 0); // #=> false
    console.log(NaN === NaN); // #=> false !!!!!
    console.log(a === 2); // #=> false
    console.log(a === true); // #=> false
    console.log(a === false); // #=> false

The && and || operators

(bet you don't expect this)


    console.log(true && false);
    console.log(false || true);
    console.log(Boolean(true && "pepe"));
    if (true && "pepe")
      console.log("true, of course!");
    console.log(Boolean(false || "pepe"));
    if (false || "pepe")
      console.log("true again, of course!");
    console.log(!(true && "pepe"));
    console.log(!(false || "pepe"));
    console.log(true && "pepe");
    console.log(false || "pepe");
  

The && and || operators

(bet you don't expect this)


    console.log(true && false); // #=> false
    console.log(false || true); // #=> true
    console.log(Boolean(true && "pepe")); // #=> 'pepe'
    if (true && "pepe")
      console.log("true, of course!"); // Executed
    console.log(Boolean(false || "pepe")); // #=> 'pepe'
    if (false || "pepe")
      console.log("true again, of course!"); // Executed
    console.log(!(true && "pepe")); // #=> false
    console.log(!(false || "pepe")); // #=> false
    console.log(true && "pepe"); // #=> 'pepe'
    console.log(false || "pepe"); // #=> 'pepe'
  

The && and || operators

(the nasty truth)

They do shortcircuit evaluation

They do not return a boolean

They do NOT RETURN A BOOLEAN...

...but the last evaluated expression

The comma operator

(very important)


    function rubyish(x) {
      return  a = !x,
              b = 22,
              console.log("Some 'statement' here"),
              a + b;
    }
    console.log(rubyish(true));
    for (var i = 0, j;
          i < 10;
          j ? j : j = 0,
          console.log(j),
          i++,
          j = i * 2);
  

The void operator

(very useful)

    var helloWorld = void function() {
      return 'Hello world';
    };
    console.log(helloWorld());
    var helloWorld = function (x) {
      if (x === undefined) return 'Hello world';
      return 'HaHaHa!';
    };
    console.log(helloWorld()); // 'Hello world'
    var undefined = true;
    console.log(helloWorld()); // 'HaHaHa'
    var helloWorld2 = function (x) {
      if (x === void 0) return 'Hello world';
      return 'HaHaHa!';
    };
    console.log(helloWorld2()); // 'Hello world'

Invasion of the Body Snatchers

(don't fall asleep)

Original by BY-YOUR-⌘: http://bit.ly/12M9bs3

Hashes

(every language has them!)


    var hash = {},
        key1 = {},
        key2 = [];

    hash[1] = "Uno (number)";
    hash["juan"] = "John";
    hash[key1] = "Key 1 value";
    hash[key2] = "Key 2 value";

    console.log(hash[1]);
    console.log(hash["juan"]);
    console.log(hash[key1]);
    console.log(hash[key2]);
  

Hashes

(every language has them!)


    var hash = {},
        key1 = {},
        key2 = [];

    hash[1] = "Uno (number)";
    hash["juan"] = "John";
    hash[key1] = "Key 1 value";
    hash[key2] = "Key 2 value";

    console.log(hash[1]); // #=> 'Uno (number)'
    console.log(hash["juan"]); // #=> 'John'
    console.log(hash[key1]); // #=> 'Key 1 value'
    console.log(hash[key2]); // #=> 'Key 2 value'
  

Hashes

(but JS has surprises!)


    var hash = {},
        key1 = {},
        key3 = { name: "juan" },
        key4 = ["juan"];

    hash["1"] = "Uno (string)";
    hash[key3] = "Mary";
    hash[key4] = "Beth";

    console.log(hash["1"]);
    console.log(hash[1]);
    console.log(hash[key3], hash[key1]);
    console.log(hash[key4], hash["juan"]);
  

Hashes

(but JS has surprises!)


    var hash = {},
        key1 = {},
        key3 = { name: "juan" },
        key4 = ["juan"];

    hash["1"] = "Uno (string)";
    hash[key3] = "Mary";
    hash[key4] = "Beth";

    console.log(hash["1"]);// 'Uno (string)'
    console.log(hash[1]);// 'Uno (string)'
    console.log(hash[key3], hash[key1]);// 'Mary', 'Mary'
    console.log(hash[key4], hash["juan"]);// 'Beth', 'Beth'
  

Hashes

(the nasty truth)

They do not exist

typeof {} == 'object'

Objects are key/value pairs

Value can be any expression

Only strings are allowed as keys

obj.propName -> obj['propName']

obj[expr] -> obj[String(expr)]

Arrays/Lists

(I know they exist!)

    var arr = [];
    console.log(arr.length);
    arr[0] = "First";
    console.log(arr.length, arr[0], arr[1]);
    arr[2] = "Third";
    console.log(arr.length, arr[1], arr[2]);
    arr["name"] = "Cool array";
    console.log(arr.length, arr["name"]);
    arr["5"] = "Weird property";
    console.log(arr.length, arr["5"]);
    var key1 = {
      toString: function () {
        return "0";
      }
    };
    arr[key1] = "First again";
    console.log(arr.length, arr[0]);

Arrays/Lists

(I know they exist!)

var arr = [];
console.log(arr.length);//0
arr[0] = "First";
console.log(arr.length,arr[0],arr[1]);//1,'First',undefined
arr[2] = "Third";
console.log(arr.length,arr[1],arr[2]);//3,undefined,'Third'
arr["name"] = "Cool array";
console.log(arr.length, arr["name"]);//3,'Cool array'
arr["5"] = "Weird property";
console.log(arr.length,arr["5"]);//6,'Weird property'
var key1 = {
  toString: function () {
    return "0";
  }
};
arr[key1] = "First again";
console.log(arr.length, arr[0]);//6,'First again'

Arrays

(the nasty truth)

They do not exist

typeof [] == 'object'

Only strings can be indexes

obj[index] -> obj[String(index)]

"Arrays"

(at least they feel like one)


    var arr = ["First", "Second", "Third"];
    console.log(arr.length, arr);
    arr.length--;
    console.log(arr.length, arr);
    arr.length++;
    console.log(arr.length, arr);
  

"Arrays"

(at least they feel like one)


    var arr = ["First", "Second", "Third"];
    console.log(arr.length, arr);
    // #=> 3, ["First", "Second", "Third"]
    arr.length--;
    console.log(arr.length, arr);
    // #=> 2, ["First", "Second"]
    arr.length++;
    console.log(arr.length, arr);
    // #=> 3, ["First", "Second", undefined]
  

Loops

    var arr = ["First", "Second", "Third"],
        obj = {
          "0": "1st",
          "1": "2nd",
          "2": "3rd",
          "length": 3,
          "name": "My name",
          "age": 22
        }, p;
    arr.name = "Cool!";
    for (p in arr)
      console.log(p);
    for (p in obj)
      console.log(p);
    for (p = 0; p < arr.length; p++)
      console.log(arr[p]);
    for (p = 0; p < obj.length; p++)
      console.log(obj[p]);

Functional gore

(a bit of spicy sauce)

Original by Javier Lastras: http://bit.ly/VmNWty

Spooky vars

(Scope)


    var outer = 'Hi there!';
    function fn(){
      var inner="I'm local";
      console.log(inner, outer);
      if(true) {
        var other="other local";
        console.log(other);
      }
      console.log(other);
    }
    fn();
  

Spooky vars

(Scope)


    var outer = 'Hi there!';
    function fn(){
      var inner="I'm local";
      console.log(inner, outer); // "I'm local", 'Hi there!'
      if(true) {
        var other="other local";
        console.log(other);  // 'other local'
      }
      console.log(other); // 'other local'
    }
    fn();
  

Spooky vars

(Hoisting)


    var x = 'Hi there !';
    function fn(){
      console.log( x );
      var x = 'New Value';
      console.log( x );
    }
    fn();
  

Spooky vars

(Hoisting)


    var x = 'Hi there !';
    function fn(){
      console.log( x ); // undefined
      var x = 'New Value';
      console.log( x ); // 'New value'
    }
    fn();
  

Named Function Expressions

(we don't need Y-Combinator)


    var sum = function sum(n) {
      if (!n)
        return 0;
      return n + sum(n - 1);
    };
    console.log(sum(4));
    var sum2 = sum;
    sum = function () {
      return 0;
    };
    console.log(sum(4));
    console.log(sum2(4));
  

Named Function Expressions

(we don't need Y-Combinator)


    var sum = function sum(n) {
      if (!n)
        return 0;
      return n + sum(n - 1);
    };
    console.log(sum(4)); // #=> 10
    var sum2 = sum;
    sum = function () {
      return 0;
    };
    console.log(sum(4)); // #=> 0
    console.log(sum2(4)); // #=> 10
  

IIFEs

(this is what we call idiomatic)


    var x = +function(x) {return x;}('11'),
        y = 22,
        z = void function(z) {y+=z; return z;}(y);
    console.log(x);
    console.log(y);
    console.log(z);
  

IIFEs

(this is what we call idiomatic)


    var x = +function(x) {return x;}('11'),
        y = 22,
        z = void function(z) {y+=z; return z;}(y);
    console.log(x); // #=> 11 (number)
    console.log(y); // #=> 44 (number)
    console.log(z); // #=> undefined
  

Modules using IIFEs

(this is what we call idiomatic)


    var pkg = function () {
      var somePrivateVar="private";

      function privateHelper() {}

      return {
        publicMethod:function() {
        },
        otherPublicMethod:function() {
        }
      };
    }();
  

Closures & Loops

(wrong)


    function makeLoggersFor(arr) {
      var size = arr.length, r = [];
      for (var i = 0; i < size; i++) {
        r[i] = function () {
          console.log(i, arr[i]);
        };
      }
      return r;
    }
    var logs = makeLoggersFor([1, 2]);
    logs[0]();
    logs[1]();
  

Closures & Loops

(sloppy but works)


    function makeLoggersFor(arr) {
      var size = arr.length, r = [];
      for (var i = 0; i < size; i++) {
        r[i] = function (i, el) {
          return function () {
            console.log(i, el);
          };
        }(i, arr[i]);
      }
      return r;
    }
    var logs = makeLoggersFor([1, 2]);
    logs[0]();
    logs[1]();
  

Closures & Loops

(idiomatic)


    function makeLoggersFor(arr) {
      return arr.map(function (el, i) {
        return function () {
          console.log(i, el);
        };
      });
    }
    var logs = makeLoggersFor([1, 2]);
    logs[0]();
    logs[1]();
  

Closures & Loops

(non leaky)


    var makeLoggersWithStyleFor = function () {
      function makeLogger(el, i) {
        return function () {
          console.log(i, el);
        };
      }

      return function (arr) {
        return arr.map(makeLogger);
      };
    }();
    var logs = makeLoggersWithStyleFor([1, 2]);
    logs[0]();
    logs[1]();
  

No methods

(freedom for the functions)


    var thisIsAnObject = {
      name: 'pepe',
      sayHi: function(msg) {
        console.log("Hi, I'm ", this.name, msg);
      }
    };
    var otherObject = {
      name: 'john',
      sayBye: thisIsAnObject.sayHi
    };
    thisIsAnObject.sayBye('!!!');
    otherObject.sayBye('?');
  

This

(where it is?)


    var thisIsAnObject = {
      name: 'pepe',
      sayHi: function(msg) {
        console.log("Hi, I'm ", this.name, msg);
      }
    }, otherObject = { name: 'john' };
    var speak=thisIsAnObject.sayHi;
    speak('nice to meet you');
    speak.call(otherObject, 'nice to meet you');
    speak.apply(otherObject, ['nice to meet you']);
  

Conclusions

(learn JS)

Original by Mike Ambs: http://bit.ly/10IWmrY

They will not save you

(no silver bullet)

  • Frameworks don't solve these problems
  • CoffeeScript !== JavaScript
  • but CoffeeScript == JavaScript

CoffeeScript

(the popular kid)

  • Better syntax (for Ruby & Python people)
  • Cool features (strings, splat, comprehensions, etc)
  • No hoisting WTF
  • No global variables
  • Safe undefined
  • == is always ===
  • Neither void nor comma operators
  • Fat arrow (=>) preserves this

CoffeeScript

(the nasty truth)

  • Type conversion hell
  • Mostly syntax sugar
  • Almost the same semantics that JS
  • Most of its cool features in ES6

CoffeeScript WTF!!

(it can't be!!)


    john =
      name: 'John'
      salutator: ->
        console.log _this
        _this = 'not this anymore'
        console.log _this
        (msg = 'Hi') => console.log msg + ' ' + @name

    johnSalutator = john.salutator()
    johnSalutator 'Bye, bye'
  

CoffeeScript WTF!!

(it can't be!!)


    john =
      name: 'John'
      salutator: ->
        console.log _this //> {...this object..}
        _this = 'not this anymore'
        console.log _this //> 'not this anymore'
        (msg = 'Hi') => console.log msg + ' ' + @name

    johnSalutator = john.salutator()
    johnSalutator 'Bye, bye' //> 'Bye, bye undefined'
  

CoffeeScript WTF!!

(still has JS inside)

Does the Coffee spec say something about this?


  console.log !new Boolean(false) //> false
  console.log 1 + !new Boolean(false) - true //> 0
  // The result of the following depends on the browser
  console.log parseInt 1 + !new Boolean(false) - true + "10"
  // Prints either 8 or 10
  

You cannot understand what's happening...

...unless you know JS

JavaScript

(some cursed features)

  • ToPrimitive
  • Operators: +, ==
  • Hoisting
  • Ancient syntax
  • Some broken functions in the library

    (parseInt, Date, etc.)

  • No plans for deprecation
  • The rest of JS is pretty good!

JavaScript

(surrender & join the dark side!)

  • You are forced to use it anyway
  • Knowing only CoffeeScript is not enough
  • Learn JS semantics!!!
  • Use some code analysis tool (JSHint/Lint, google closure, etc.)

The End

Some questions?

Original by Ethan Lofton: http://bit.ly/1arHGj