JS Puzzlers – How well do you know JavaScript? – Test, Test 123



JS Puzzlers – How well do you know JavaScript? – Test, Test 123

0 2


jspuzzlers


On Github gmdayley / jspuzzlers

JS Puzzlers

How well do you know JavaScript?

Test, Test 123

alert("A");

What will this alert?

  • A
  • B
  • C
  • D
  • Not quite sure what is going on.

Basically Wrong

var x = parseInt("5");
var y = parseInt("05");
var z = parseInt("08");

alert((x + y) * z);

What will this alert?

  • 80
  • 20
  • undefined
  • error
  • 0

Lessons learned: Basically wrong

  • parseInt() takes two parameters: number, base
  • Omitting the second parameter assumes base-10 as the default
  • Numbers starting with 0x assumed as radix 16 (hex)
  • Numbers starting with 0, assumed as radix 8 (oct)
  • There is no 8 (or 9) in octal so parseInt() just returns 0
  • Specifying the intended base as the second parameter removes the ambiguity
parseInt("08", 10) // 8

Not my type

alert([
  typeof(NaN),
  typeof(Number(123)),
  typeof(null),
  typeof([1,2,3])
].join());

What will this alert?

  • undefined, object, undefined, array
  • undefined, number, object, array
  • number, number, object, object
  • number, object, undefined, object
  • huh

Lessons learned: You're not my type

  • typeof is not very helpful
  • Use:
    var toType = function(obj) {
        return ({}).toString.call(obj)
        .match(/\s([a-zA-Z]+)/)[1].toLowerCase()
      }
  • "object" toType({a: 4});
  • "array" toType([1, 2, 3]);
  • "arguments" toType(arguments);
  • "error" toType(new ReferenceError);
  • "date" toType(new Date);

Who do we appreciate?

var $2 = 0.2;
var $4 = 0.2 + 0.2;
var $6 = 0.2 + 0.2 + 0.2;
var $8 = 0.2 + 0.2 + 0.2 + 0.2;

if      ($4 !== .4) { alert("A"); }
else if ($6 !== .6) { alert("B"); }
else if ($8 !== .8) { alert("C"); }
else                { alert("D"); }

What will this alert?

  • A
  • B
  • C
  • D

Lessons learned:

  • Number is the only numerical type
  • Number is an IEEE floating point with double precision
  • As a result:
    0.0 === 0;  //TRUE, unlike Java or C#
  • Number exhibits the floating point rounding error
  • Numerical calculations can be slow (no integer)

A round-aboutway to round

A.  var rounded = Math.round(num);
B.  var rounded = ~~ (0.5 + num);
C.  var rounded = ~~ (num + (num > 0 ? .5 : -.5));
D.  var rounded = (0.5 + num) << 0;

Which is the fastest?

  • A
  • B
  • C
  • D

A round-aboutway to round

A round-aboutway to round

Lessons learned:

  • Don't use dumb performance tricks in production
  • Math.round() is more than 50 times fasterthan it was four years ago

Loopy Eventing

var ee = new (require('events').EventEmitter);
var die = false;

ee.on('die', function() { die = true; });
setTimeout(function() { ee.emit('die'); }, 100);

while (!die) {}
console.log('done');

Which is the outcome?

  • Constructor Error on line 1
  • TypeError on line 4
  • No output. No exit.
  • Wait 100ms, then log 'done';

Lessons learned:

  • The JavaScript Event Loop does not yield
  • New events go to the end of the queue
  • Three ways to add events to the queue:
    • setTimeout(fn, ms) / setInterval(fn, ms)
    • process.nextTick(fn) (node only)
    • setImmediate(fn)

Maxed out

if (Math.min() < Math.max()) {
  alert('Of course');
}
else {
  alert('Yikes');
}

Which is the outcome?

  • alerts: Of course
  • alerts: Yikes
  • Throws error
  • None of the above

Lessons learned:

  • Math.min() & Math.max() takes a list of numbers to compare
  • Math.min() === -Infinity
  • Math.max() === Infinity
//One way to find it
var x = Math.pow(2, 53) //9007199254740092
x === x + 1 //true

//Better way
Number.MAX_VALUE;
Number.MIN_VALUE;

A-for-scope-ic surgery

for(var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 0);
}

Which is output in the console?

  • 0, 1, 2
  • 1, 2, 3
  • 2, 2, 2
  • 3, 3, 3

Lessons learned:

  • JavaScript has only function scope
  • Self executing functions can create scope
    for(var i = 0; i < 3; i++) {
      (function(x) {
        setTimeout(function() {
          console.log(x);
        }, 0);
      })(i);
    }

Loopy Performance

var a = [...];

A.  for (var i = 0; i < a.len; i++) { ... };
B.  var i = a.length; while (i--) { ... };
C.  for (var i in a) { ... };
D.  a.forEach(function(i) { ... });

Which is the slowest?

  • A. Uncached for loop
  • B. Decrementing while loop
  • C. For in loop
  • D. For each loop

Loopy Performance Race

Lessons learned:

  • Decrementing
    • Slows down for loops by 1.5%
    • Slows down while loops by almost 6 times
  • Caching a for loop slows it down
    for (var i = 0, len = a.len; i < len; i++) { ... };
  • "ForEach" and "For-in" loops are really slow

Malpractice

var x = 2;
var y = 3;
(function (){
    alert(x + y);
    x = 0;
    var y = 2;
})();

Which is true?

  • alerts: NaN
  • alerts: 5
  • alerts: 2
  • alerts: 4

Lessons learned: Malpractice

  • Re-declaring y inside the function causes unexpected behavior from the interpreter
  • The interpreter moves var statements to the beginning, causing y to be undefined:
var x = 2;
var y = 3;
(function (){
    var y;
    alert(x + y);
    x = 0;
    y = 2;
})();

Great Lengths

function fn(length, width) {
    this.length = length;
    this.width = width;
}

alert(fn.length + new fn(3, 4).length);

Which is true?

  • alerts: NaN
  • alerts: 6
  • alerts: ReferenceError: fn is not defined
  • alerts: 5

Lessons learned: Great Lengths

function fn(l, w) {
  this.length = l;
  this.width = w;
}

alert(fn.length + new fn(3, 4).length);
  • new fn(3, 4).length => 3
  • fn.length => 2
  • function.lenth => number of args in function signature.

Semi-colonoscopy

var background = function()
{
    return
    {
        color : 'blue'
    };
};
alert(background().color);

Which is true?

  • alerts: blue
  • alerts: undefined
  • throws a TypeError
  • alerts: [Object]

Lessons learned: Semi-colonoscopy

  • Semicolon's in JavaScript are optional
  • The interpreter tries to guess where there should go
  • This causes undesired results:
var background = function()
{
    return; //Thanks, but no thanks!
    {
        color : 'blue'
    };
}
So, instead of returning the object, undefined is returned, causing a TypeError to be thrown

Come Join With Me

var a = new Array(1, 2, 3);
var b = new Array(4, 5);
var c = new Array(6);
var d = a.concat(b, c);

alert(d.toString());

Which is true?

  • alerts: 1,2,3
  • alerts: 1,2,3,4,5
  • alerts: 1,2,3,4,5,6
  • None of the above

Lessons learned: Come Join With Me

  • Calling new Array() with multiple arguments, interprets them as members of the Array
  • Calling new Array() with a single Integer argument(n) initializes an array with n undefined items
var x = new Array(3);

//Produces this
[undefined, undefined, undefined].join();

//Which looks like this
"" + String(undefined) +
  "," + String(undefined) +
  "," + String(undefined);

//Resulting in
',,' === x;  //true

JS++

alert(+[]+ +'0XE'+ +true+ +'1E1');

Which is true?

  • alerts: NaN
  • alerts: "14110"
  • alerts: 90210
  • alerts: 25

Lessons learned: JS++

  • "Unary + is the fastest and preferred way of converting something into a number, because it does not perform any other operations on the number." -MDN
  • toNumber(getValue(expr))
  • +[]    == 0
  • +'0xE' == 14
  • +true  == 1
  • +'1e1' == 10

Lessons learned: JS++ Extra Credit

+new Date
  • getValue() => getTime()
  • 1317653638027

Lessons learned: JS++ (cont.)

  • A. NaN
  • B. "14110"  []+ +'0XE'+ +true+ +'1E1'
  • C. 90210   +[]+ +'OXD1'+ +true+ +'9E4'
  • D. 25          +[]+ +'0XE'+ +true+ +'1E1'

To infinity and beyond

var a = 0;
var b = 0 * -1;

if (a === b) {
  if (1 / a === 1 / b) { alert("A"); }
  else { alert("B"); }
}
else { alert("D"); }

Which is true?

  • alerts: A
  • alerts: B
  • throws an error because -0 is NaN
  • alerts: D

Lessons learned: To infinity and beyond

  • Number allows for both positive and negative zero values
  • +0 equals -0
  • However +infinity does not equal -infinity

Defunct-ion, Part A

var user = {
  name: 'Mike',
  hello: function(thing) {
    alert(this + " says hello " + thing);
  }
}
user.hello('World');

Which is true?

  • alerts: [object Object] says hello World
  • alerts: Mike says hello World
  • alerts: user says hello World
  • alerts: World says hello undefined

Lessons learned: Defunct-ion, Part A

var user = {
  name: 'Mike',
  hello: function(thing) {
    alert(this + " says hello " + thing);
  }
}
user.hello('World');
  • name: 'Mike' is a distraction.
  • this => user
  • user.toString() => [object Object]

Defunct-ion, Part B

var user = {
  name: 'Mike',
  hello: function(thing) {
    alert(this + " says hello " + thing);
  }
}
function pass(fn) {
  fn('World');
}
pass(user.hello);

Which is true?

  • alerts: [object Object] says hello World
  • alerts: [object Window] says hello World
  • alerts: undefined says hello World
  • alerts: World says hello undefined

Lessons learned: Defunct-ion, Part B

var user = {
  name: 'Mike',
  hello: function(thing) {
    return (this + " says hello " + thing);
  }
}
function pass(fn) {
  fn('World');
}
pass(user.hello);
  • Calling hello with user context:
    • user.hello('World')
    • hello.call(user, 'World')
  • But passing a function will lose context
  • So use hello.bind(user)

Defunct-ion, Part C

var user = {
  name: 'Mike',
  hello: function(thing) {
    alert(this + " says hello " + thing);
  }
}
user.hello.call('World');

Which is true?

  • alerts: [object Object] says hello World
  • TypeError: undefined_method
  • alerts: undefined says hello World
  • alerts: World says hello undefined

Lessons learned: Defunct-ion, Part C

var user = {
  name: 'Mike',
  hello: function(thing) {
    alert(this + " says hello " + thing);
  }
}
user.hello.call('World');
  • call passes context in the first parameter.
  • user.hello.call(user, 'World') == user.hello('World')
  • user.hello.call(user.name, 'World') => "Mike says hello World"

Guilty by Association

function Playlist(name) {
    this.name = name;
    this.songs = [];
    this.addSong = function(id, song) {
        this.songs[id] = song;
    }
    this.showPlaylist = function() {
        alert(this.songs.join(','));
    }
}

Guilty by Association

var plist = new Playlist("90s");

plist.addSong('311', 'Beautiful Disaster');
plist.addSong('join', 'Scratch');
plist.addSong('U2', 'Numb');

plist.showPlaylist();

Which is true?

  • Throws a TypeError
  • alerts: Beautiful Disaster, Scratch, Numb
  • alerts: Numb, Scratch, Beautiful Disaster
  • alerts: 311, join, U2

Lessons learned: Guilty by Association

  • The problem is songs is an associative array
  • When we used 'join' as one of the keys, we effectively modified the built-in join method of Array
  • Don't use arrays in these situations, instead use an object:
this.songs = {};

Thinking Inside the Box

alert(++[[]][+[]]+[+[]]);

Which is true?

  • alerts: NaN
  • alerts: "10"
  • alerts: "0+0+0"
  • Don't be ridiculous. That will not evaluate!

Lessons learned: Thinking Inside the Box

alert(++[[]][+[]]+[+[]]);
  • Unary + operator
  • ToValue() converts empty array to empty string
  • ToNumber() converts empty array to 0
  • ++[[]][+[]]+[+[]] => ++[[]][0]+[0]

Lessons learned: Thinking Inside the Box (cont.)

alert(++[[]][+[]]+[+[]]); => alert(++[[]][0]+[0]);
  • ToValue() converts empty array to empty string
  • ++[[]][0]+[0] => ++[""][0]+[0]
  • ++ increments and stores
  • ++[""][0]+[0] => 1+[0]
  • ToValue() converts 0 array to "0" string
  • 1+[0] => 1+"0" => "10"

Never Judge a Book by it's Cover

function Book(title){
    this.title = title;
    return { title: 'JavaScript: The Good Parts'};
}
function ZipCode(zip){
    this.zip = zip;
    return 84043;
}
var book = new Book('Twilight');
var zip = new ZipCode(07849);

if(book instanceof Book){ alert('Book')};
if(zip instanceof ZipCode){ alert('ZipCode')};

Which is true?

  • alerts: Book and ZipCode
  • alerts: Book
  • alerts: ZipCode
  • Does not alert anything

Lessons learned: Never Judge a Book by it's Cover

  • Return values in constructor functions can be overridden
  • Unless...you return a primative type: string, number, date
  • Returning a primative is ignored and the original initialized object is returned

Lessons learned: Never Judge a Book by it's Cover

  • This is often used to implement private and public members:
function Foo() {
    var privateVar = 'World';
    function privateMethod() {}

    return {
        publicVar : 'Hello',
        getString : function() {
            return publicVar + ' ' + privateVar;
        }
    };
}
However, you can not use instanceof to check if its of type Foo

Double Agent

if("1" == 1) {
    if("" == 0) {
        if("" == "0") alert("A");
        else alert("B");
    }
    else alert("C");
}
else alert("D");

Which is true?

  • alerts: A
  • alerts: B
  • alerts: C
  • alerts: D

Lessons learned: Double Agent

  • The double equals (==) performs comparisons after type coercion. With the == operator you can compare objects of different types.
  • This is often not what you intended and can lead to undesired results.
  • Instead you should make sure that your operands are of the right type before doing comparisons or use the triple equals operator (===) to do the comparisons.
"1" === 1 // false
Number("5") === 5  // true

Getting Closure

var x = 'globalX', y = 'globalY';
function outer() {
  this.x = 'funcX';
  var y = 'funcY';
  this.inner = function() {
    alert(x + ":" + y);
  }
}
new outer().inner();

Which is true?

  • alerts: globalX:globalY
  • alerts: globalX:funcY
  • alerts: funcX:globalY
  • alerts: funcX:funcY

Lessons learned: Getting Closure

var x = 'globalX', y = 'globalY';
function outer() {
  this.x = 'funcX';
  var y = 'funcY';
  this.inner = function() {
    alert(x + ":" + y);
  }
}
new outer().inner();
  • this.x can be explicitly called from inner
  • this is not explicitly used
  • this.x cannot be used without context