The Quirky Side of ES2015

whoami

  • Zirak
  • I like JS
  • http://zirak.me
  •  

  • Did I forget to record my screen?
  • But enough about me

ES2015 has been widely covered

  • We know all about the cool features
    • Arrow functions, let and const, destructuring, ...
  • But this is still JavaScript. What about the ugly side that we know and love?
  • Where's the wat?

> [] + []
''
> [] + {}
'[object Object]'
> {} + []
0
> {} + {}
NaN
            

Default Arguments


// "Default values" are assigned at *run time*, unlike the classic python example:
function foo(x=[]) { x.push(1); return x; }
console.log(foo()); // [1]
console.log(foo()); // [1], in python: [1, 1]
                

// And the default values can be any expression!
var x = 0;
var incX = () => x++;
function blah(a=incX()) { return x; }

console.log(blah()); // 0
console.log(blah()); // 1
                

// And you can reference any previously declared parameter
let isWut = (a, b=a || 12) => b;

isWut(true); // true
isWut(''); // 12
                

Ugly Code


let sum = (a, b, s=a+b) => s;
sum(10, 20); // 30
                

let fib = (n, s=n > 2 ? sum(fib(n-1), fib(n-2)) : 1) => s;
                

let f = (a=(() => wut(12))(), b=(function (x, ...[{ dis: { is: y } }]) {
    return x ? ami(y) : 3
})(true instanceof {
    [Symbol.hasInstance()](x) { return !!x }
}), { dis: { is: 'wat' } }) => false;
                

var Y = function(F) {
    return function(f) {
        return f(f);
    }(function(f) {
        return F(
            function(x) { return (f(f))(x); }
        );
    });
};
                

// I'm curious, what about eval?
function foo(a=eval('var x = 12; x')) {
    console.log(a); // 12
    console.log(x); // ReferenceError: x is not defined
    // where's x?
}
foo();
                

// Maybe it's only defined in the parameters?
function foo(a=eval('var x = 12; x'), b=x) {
    console.log(b);
}
foo(); // ReferenceError: x is not defined
                

// What about shadowing declarations?
var x = 17;
function foo(a=x, x=12) {
    console.log(a);
}

foo(); // ReferenceError: x is not defined
// !?

use strict


// One of strict mode's implications:
function sloppyFoo() { 'use sloppy'; return this; }
function strictFoo() { 'use strict'; return this; }

sloppyFoo(); // global object
strictFoo(); // undefined
                

// But what about this:
function foo(a=this) {
    'use strict';
    return a;
}

'use sloppy';
foo(); // undefined? global object?
                

// And what about this?
function foo(a=(function() {
    let x = 12;
    with ({ x: 42 })
        return x;
    // with is a syntax error in strict mode
})) {
    'use strict';
    return a;
}
                
Syntax Error

Symbols

Symbol.hasInstance


// Allows you to override `instanceof`
let awesomeObject = {
    [Symbol.hasInstance](thing) {
        return thing.x > thing.y;
    }
};

{ x: 80, y: -1 } instanceof awesomeObject; // true
{ x: 10, y: 79 } instanceof awesomeObject; // false

// Let's abuse it
        

// Randomly switch my mind about my children
var thing = {
    [Symbol.hasInstance]() { return Math.random() > 0.5; }
};

4 instanceof thing; // true? false? Who knows!
        

const Even = {
    [Symbol.hasInstance](x) { return !(x % 2); }
};

4 instanceof Even; // true
5 instanceof Even; // false
            

// Something I couldn't figure out: Why isn't it called on functions?
function foo() {}
foo[Symbol.hasInstance] = function (x) {
    console.log('wut', x);
    return x === 4;
};

4 instanceof foo; // false, no logs
            
  • I'll be honest: I have no idea why
  • Spec:
    1. If Type(C) is not Object, throw a TypeError exception.
    2. Let instOfHandler be GetMethod(C, @@hasInstance).
    3. If instOfHandler is not undefined, then
      1. Return ToBoolean(Call(instOfHandler, C, « O »)).
    4. If IsCallable(C) is false, throw a TypeError exception.
    5. Return ? OrdinaryHasInstance(C, O).

// v8 source
MaybeHandle<Object> Object::InstanceOf(Isolate* isolate, Handle<Object> object,
                                       Handle<Object> callable) {
  // The {callable} must be a receiver.
  if (!callable->IsJSReceiver()) {
    THROW_NEW_ERROR(isolate,
                    NewTypeError(MessageTemplate::kNonObjectInInstanceOfCheck),
                    Object);
  }

  // Lookup the @@hasInstance method on {callable}.
  Handle<Object> inst_of_handler;
  ASSIGN_RETURN_ON_EXCEPTION(
      isolate, inst_of_handler,
      JSReceiver::GetMethod(Handle<JSReceiver>::cast(callable),
                            isolate->factory()->has_instance_symbol()),
      Object);
  if (!inst_of_handler->IsUndefined(isolate)) {
    // Call the {inst_of_handler} on the {callable}.
    Handle<Object> result;
    ASSIGN_RETURN_ON_EXCEPTION(
        isolate, result,
        Execution::Call(isolate, inst_of_handler, callable, 1, &object),
        Object);
    return isolate->factory()->ToBoolean(result->BooleanValue());
  }

  // The {callable} must have a [[Call]] internal method.
  if (!callable->IsCallable()) {
    THROW_NEW_ERROR(
        isolate, NewTypeError(MessageTemplate::kNonCallableInInstanceOfCheck),
        Object);
  }

  // Fall back to OrdinaryHasInstance with {callable} and {object}.
  Handle<Object> result;
  ASSIGN_RETURN_ON_EXCEPTION(
      isolate, result,
      JSReceiver::OrdinaryHasInstance(isolate, callable, object), Object);
  return result;
            

¯\_(ツ)_/¯

A Revelation Occurs!
  • As it turns out, Function.prototype[Symbol.hasInstance] is defined as non-writable!

Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance)
// { writable: false, enumerable: false, configurable: false, value: function [Symbol.hasInstance]() }

// Writing raises an error in strict mode:
(() => {
    "use strict";
    function foo() {}
    foo[Symbol.hasInstance] = function(){};
    // TypeError: Cannot assign to read only property
})();
            
  • Lesson learned: Run code in strict mode or you'll look a-fool in presentations

Symbol.replace


// Defined on RegExp.prototype, called in str.replace
'foobar'.replace({
    [Symbol.replace](subj, rep) {
        return `wut${rep}`;
    }
}, 123); // wut123
// Friends & Family: Symbol.{match,search,split}
            

function countedReplace(pattern, count) {
    return {
        pattern, count,
        [Symbol.replace](subj, rep) {
            let c = this.count;
            // In This Function: Avoiding subclassing RegExp
            let p = new RegExp(this.pattern, this.pattern.flags.replace(/g|$/, 'g'));

            return subj.replace(p, $0 => c <= 0 ? $0 : (c--, rep));
        }
    }
}

'foo bar baz quz qux'.replace(countedReplace(/\w+/, 2), 'wut');
// wut wut baz quz qux
            
  • Never used anywhere, ever.

¯\_(ツ)_/¯

Symbol.isConcatSpreadable


Window.prototype[Symbol.isConcatSpreadable] = true;
[].concat(window); // list of frames

// functions have length too!
function foo(a, b, c){}
[].concat(foo); // [foo]
Function.prototype[Symbol.isConcatSpreadable] = true;
[].concat(foo); // [ undefined ✗ 3 ], notice that it's not undefined values but keys
            

// Maybe flipping it to false?
var foo = [0, 1, 2];
[-2, -1].concat(foo); // [-2, -1, 0, 1, 2]

foo[Symbol.isConcatSpreadable] = false;
[-2, -1].concat(foo); // [-2, -1, [0, 1, 2]]
// But then why not put the argument in an array?
            

¯\_(ツ)_/¯

Symbol.unscopables

  • One of the weirdest symbols out there
  • Hides properties from with
  • 
    var thing = {
        foo: 4
    };
    
    with (thing) {
        console.log(foo); // 4
    }
    
    thing[Symbol.unscopables] = { foo: true };
    
    with (thing) {
        console.log(foo); // ReferenceError: foo is not defined
    }
                    
  • Why?
    • Only used once in the spec to hide new Array functions
    • Perhaps to prevent breaking the web?

¯\_(ツ)_/¯

Functions

  • We received many new ways to declare functions:
    • Arrow functions
      • let arrowFunc = (a, b) => { things }
    • Method declarations
      • var obj = { method() { things } };
    • Generators
      • function *gen() { yield things }
    • Async functions
      • async function neat() { await things }
    • Classes
      • class Thingy { methods }
    • Turns out they're a bit different from regular functions and each other

// Classes can't be called:
class Foo {}
Foo(); // TypeError

// Additionally, they're not hoisted, just like `let`:
(function() {
    new Foo(); // ReferenceError
    class Foo {}
})();
        

// Methods, generators, async functions and arrows can't be `new`ed:
var obj = {
    meth() {}
};
function *gen() {}
async function asy() {}
var arrow = () => {};

new obj.meth(); // TypeError
new gen(); // TypeError
new asy(); // TypeError
new arrow(); // TypeError
        

// ...but generators do have a prototype
obj.meth.prototype; // undefined
arrow.prototype; // undefined
asy.prototype; // undefined
gen.prototype; // Generator
// wut?
        

// Generators also have an extra step in their inheritance hierarcy:
Object.getPrototypeOf(gen); // GeneratorFunction
Object.getPrototypeOf(Object.getPrototypeOf(gen)); // Function.prototype
// GeneratorFunction is not exposed on the global object :(
        

// It can still be used, just like the regular Function constructor!
var GeneratorFunction = Object.getPrototypeOf(gen).constructor;
var gen = GeneratorFunction('let x = yield 4; return x;');
var g = gen();
console.log(g.next()); // { value: 4, done: false }
console.log(g.next(12)); // { value: 12, done: true }
        

// This also applies to async functions:
var AsyncFunction = Object.getPrototypeOf(asy).constructor;
var wut = AsyncFunction('return await 4')
wut().then(r => console.log(r)); // 4
        
  • Wait a minute...what does yield do in regular functions?
  • 
    (function () {
        yield 4; // SyntaxError: Unexpected number
    })();
            
    
    (function () {
        var x = yield; // ReferenceError: yield is not defined
        return x;
    })();
            
    
    var yield = 4;
    console.log(yield); // 4
    // wut?
            
  • Did you miss browser inconsistencies?
  • Firefox treats the first two as Generators
  • Have fun!
  • Wait another minute...if we can do that with yield...
  • 
    function let() { return 4; }
    console.log(let()); // 4
            
    
    var let = 4;
    console.log(let); // 4
            
  • ¯\_(ツ)_/¯

extends


// The right operand (RHS) of extends can be any expression
class Foo extends (false || Number) {}
// Remember writing bad code abusing default arguments? :D
        

// Extending null has a special meaning, similar to Object.create(null)
class Regular {}
Object.getPrototypeOf(Regular.prototype); // Object.prototype

class Foo extends null {}
Object.getPrototypeOf(Regular.prototype); // null

// ...but leads to funky shit
new Foo; // TypeError: super is not a constructor

class Bar extends null { constructor() {} }
new Bar; // ReferenceError: this is not defined
// wut?

// Open question: How do we properly implement Foo or Bar?
        

A small side note on scope


class Foo extends 4 {} // TypeError, legit
        

class Foo {} // SyntaxError: Foo already declared
        

Foo // ReferenceError: Foo is not defined
// wut?
        

// Also works with the other temporal-dead-zone annhiliating declarations, let and const
let foo = (() => { throw 'wut'; })(); // UncaughtError

foo; // ReferenceError
foo = 4; // SyntaxError: foo has already been declared
        
  • Can be (ab)used to prevent a global by a certain name from being declared

super

// Usually used inside classes
// Inspired by http://www.sitesbay.com/java/java-super-keyword
class Student {
    message() {
        console.log("Good Morning Sir");
    }
}
class Faculty extends Student {
    message() {
        console.log("Good Morning Students");
    }
    display() {
        this.message();//will invoke or call current class message() method
        super.message();//will invoke or call parent class message() method
    }
}

// I had to fix a bug in this part, s/Student/Faculty/ sorry sitesbay.com
let f = new Faculty();
f.display();
// Good Morning Students
// Good Morning Sir

super outside classes


var parent = {
    x: 4
};
var child = {
    __proto__: parent,
    x: 7,
    whatsX() {
        console.log(super.x); // 4
    },
    heynow: function () {
        console.log(super.x); // SyntaxError, super can only be used inside methods
    }
};
        

// super is computed from a hidden property called [[HomeObject]] which refers
//to the object at *function creation time*, so the following will not work as
//you expect:
var riddle =
    wut() {
        console.log(super.x); // Object.getPrototypeOf(riddle).x === undefined
    }
};
Object.assign(child, riddle);
        

Abusing super


var foo = {
    x: 4,
    whatsX() {
        return super.x;
    }
};

var p = new Proxy(foo, {});
Object.setPrototypeOf(foo, p);

// Inside of `foo`'s methods, `super` refers to the proxy, which delegates to `foo`
// Meaning: We sort of get a reference to [[HomeObject]]!
        

foo.whatsX(); // 4
foo.whatsX.call({ x: 12 }); // 4

var wut = foo.whatsX.bind({ x: 13 });
wut(); // 4

// win
        

Legacy

Elephants in the room since 1999

break


// `break` and `continue` can be used with labels to control loops:
outer: for (...) {
    for (...) {
        break outer;
    }
}
// Not frequently used, regarded as bad practice.

// Now tell me, what does this do (hint: not a syntax error)?
foo: {
    console.log(1);
    break foo;
    console.log(2);
}

foo: function foo() {
    foo: {
        console.log(1);
        break foo;
        console.log(2);
    }
}

foo: try { throw 'wut'; }
catch (e) {
    console.log(1);
    break foo;
    console.log(2);
}

Comments

  • Remember this?
    
    <script type="text/javascript">
    <!-- <![CDATA[
    // awesome code goes here
    ]] -->
    </script>
                
  • Ever wondered how it works? After all, it's invalid javascript.
  • Or is it?
    
    console.log(0);
    <!-- console.log('wtf is this!?')
    console.log(1);
    --> console.log('i will not run!');
    console.log(2);
                
  • <!-- and --> are parsed as line comments!

Fun times with comments

  • So we've got a comment which works in both javascript and html
  • Let's make a file that's both js and html!
    
    <!--
    console.log('wat');
    --> <b>wat</b>
            
  • Serve as js
    • console.log('wat')
  • Serve as html
    • <b>wat</b>

Thank you

Questions?