The Quirky Side of ES2015

Up-to-date version can be found here:

http://zirak.me/quirky-slides

whoami

 

  • 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;
var Y = function(F) {
    return function(f) { 
        return f(f); 
    }(function(f) {
        return F(
            function(x) { return (f(f))(x); }
        );
    });
};
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;
// 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 awsesomeObject = {
    [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).

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;
}

¯\_(ツ)_/¯

Symbol.replace

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
// 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}

¯\_(ツ)_/¯

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

// Actual use: NodeList, HTMLCollection? nah, you got Array.from and slicing
// 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]]

¯\_(ツ)_/¯

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 several 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 }
    • Class keyword
      • 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() {}
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
(function () {
    yield 4; // SyntaxError: Unexpected number
})();
  • Wait a minute...what does yield do in regular functions?
(function () {
    var x = yield; // ReferenceError: yield is not defined
    return x;
})();
  • Did you miss browser inconsistencies?
  • Firefox treats both as Generators
  • Have fun!
  • Side note: This only works in sloppy mode, in strict mode yield is a reserved word.

new.target

// Used in classes:
class Rectangle {
    constructor() {
        console.log(new.target);
    }
}

class Square extends Rectangle {
    constructor() {
        super();
    }
}

new Rectangle(); // Rectangle
new Square(); // Square
// Can be used to determine if you were `new`ed:
function foo() {
    console.log(new.target);
}

foo(); // undefined
new foo(); // foo

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 = {
    x: 7,
    whatsX() {
        console.log(super.x); // 4
    },
    heynow: function () {
        console.log(super.x); // SyntaxError, super can only be used inside methods
    }
};
Object.setPrototypeOf(child, parent);
// super is attached at *object 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

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

class Foo extends 4 {} // TypeError, legit

class Foo {} // SyntaxError: Foo already declared

Foo // ReferenceError: Foo is not defined
// wut?
// Also works for 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

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);
}
// What about this?
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?

 

 

 

  • Ever wondered how it works? After all, it's invalid javascript.
  • Or is it?
<script type="text/javascript">
<!-- <![CDATA[
// awesome code goes here
]] -->
</script>
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. wat?

Don't do this

  • 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

http://slides.com/zirakertan/the-quirky-side-of-es2015

The Quirky Side of ES2015

By Zirak Ertan

The Quirky Side of ES2015

  • 988