Slide 1

Slide 1 text

That's So Meta JavaScript Metaprogramming @nicknisi

Slide 2

Slide 2 text

Metaprogramming is the writing of computer programs with the ability to treat programs as their data. It means that a program could be designed to read, generate, analyse or transform other programs, and even modify itself while running. — Wikipedia

Slide 3

Slide 3 text

Metaprogramming is writing code that writes code

Slide 4

Slide 4 text

Welcome to my talk on Vim macros

Slide 5

Slide 5 text

Code that writes code • Transpilers babel/TypeScript

Slide 6

Slide 6 text

Metaprogramming is writing code that manipulates language constructs at runtime

Slide 7

Slide 7 text

Welcome to my talk on eval

Slide 8

Slide 8 text

Metaprogramming with eval const foo = { bar: true }; const name = document.querySelector('#inp1').value; const value = document.querySelector('#inp2').value; eval(`foo.${name} = ${value}`);

Slide 9

Slide 9 text

Reflection

Slide 10

Slide 10 text

Reflective Metaprogramming The ability for code to examine and modify its own structure and behavior at runtime • Introspection: read the structure of a program • Self-modification: change program structure • Intercession: change language semantics

Slide 11

Slide 11 text

These are things you probably use Every day

Slide 12

Slide 12 text

Introspection Read the structure of a program const tvShows = { 'Mr. Robot': 20, 'The Simpsons': 598, 'House of Cards': 52 }; Object.keys(tvShows).forEach(title => { const episodes = tvShows[title]; console.log(`${title} has been on air for ${episodes} episodes.`); });

Slide 13

Slide 13 text

Self-modification change program structure const foo = { bar: true }; const name = document.querySelector('#inp1').value; const value = document.querySelector('#inp2').value; //eval(`foo.${name} = ${value}`); foo[name] = value;

Slide 14

Slide 14 text

Intercession change language semantics const obj = { _foo: true, get foo() { console.log('getting value for foo'); return this._foo; } } console.log(obj.foo); // => getting value for foo // => true

Slide 15

Slide 15 text

Intercession const obj = {}; Object.defineProperty(obj, 'foo', { writable: false, enumerable: false, value: true, configurable: false }); console.log(obj.foo); // true obj.foo = false; // will throw TypeError in strict mode console.log(obj.foo); // true

Slide 16

Slide 16 text

Object Reflection methods The Object object in JavaScript contains several Reflection methods • Object.keys (introspection) • Object.entries (introspection) • Object.defineProperty (intercession) • Object.freeze (intercession)

Slide 17

Slide 17 text

So what does ES6/7/8/2015/2016/2017 give us?

Slide 18

Slide 18 text

Here be DRAGONS

Slide 19

Slide 19 text

Reflect (Introspection)

Slide 20

Slide 20 text

Reflect • Static object containing JavaScript Reflection methods • Contains some similar methods to Object, but with saner return values

Slide 21

Slide 21 text

Reflect Reflect.apply Reflect.has Reflect.construct Reflect.isExtensible Reflect.defineProperty Reflect.ownKeys Reflect.deleteProperty Reflect.preventExtensions Reflect.get Reflect.set Reflect.getOwnPropertyDescriptor Reflect.setPrototypeOf Reflect.getPrototypeOf

Slide 22

Slide 22 text

Reflect.defineProperty const obj = {}; const success = Reflect.defineProperty(obj, 'foo', { writable: false, enumerable: false, value: true configurable: false }); if (success) { console.log(obj.foo); // true obj.foo = false; // will throw TypeError in strict mode console.log(obj.foo); // true }

Slide 23

Slide 23 text

Reflect.apply function greeting(name) { return `${this.greeting}, ${name}!`; } const context = { greeting: 'Hello' }; const args = [ 'Nick' ]; Reflect.apply(greeting, context, args); // => Hello, Nick!

Slide 24

Slide 24 text

Reflect.deleteProperty // the ES5-way -- wrapping the delete keyword function deleteProp(obj, propName) { delete obj[propName]; } const obj = { foo: true, bar: false }; deleteProp(obj, 'foo'); // without the method wrapper Reflect.deleteProperty(obj, 'bar');

Slide 25

Slide 25 text

Reflect.construct class CatList extends Array { [Symbol.iterator]() { let i = 0, values = this.map(val => `${val} kitty`); return { next() { if (i < values.length) return { value: values[i++], done: false }; return { done: true }; } }; } } let result = Reflect.construct(CatList, ['foo', 'bar']); console.log(...result); // => foo kitty bar kitty console.log(result instanceof CatList); console.log(Array.isArray(result));

Slide 26

Slide 26 text

Symbol (Self-Modification)

Slide 27

Slide 27 text

What is a Symbol? • A new primitive (like Number, Boolean, String) • A completely unique, immutable value • Cannot be coerced into a String • Symbol() creates a new Symbol, but don't use the new keyword

Slide 28

Slide 28 text

Symbol usage let a = Symbol(); console.log(a); // => Symbol() let b = Symbol('some description'); console.log(b); // => Symbol(some description) console.log(a === Symbol()); // => false console.log(b === Symbol('some Description')); // => false // using the global, realm-crossing Symbol registry let c = Symbol.for('foo'); console.log(c === Symbol.for('foo')); // => true console.log(Symbol.keyFor(c)); // => foo let foo = 'foo ' + Symbol(); // => TypeError

Slide 29

Slide 29 text

What is Symbol good for? Creating completely unique values and properties const registry = {}; // Or a Map :-) class SomeComponent { constructor() { this.id = Symbol(); // guaranteed to be unique registry[this.id] = this; } // ... }

Slide 30

Slide 30 text

What is Symbol good for? const SUCCESS = Symbol('success'); const FAILURE = Symbol('failure'); const colors = { [SUCCESS]: '#0F0', [FAILURE]: '#F00' }; function output(type, message) { console.log(`%c${message}`, `color:${colors[type]}`); } output(SUCCESS, 'This is an error message'); output(FAILURE, 'This is a success message');

Slide 31

Slide 31 text

Symbols and Objects Symbols can be used as properties on objects let sym = Symbol(); const obj = { [sym]() { return 'hello world'; } }; console.log(obj[sym]()); // => hello world

Slide 32

Slide 32 text

Well-known Symbols* *not actually that well known

Slide 33

Slide 33 text

Well-known Symbols • Symbol.hasInstance • Symbol.iterator • Symbol.match • Symbol.replace • Symbol.species • Symbol.toPrimitive ...And many more

Slide 34

Slide 34 text

Symbol.hasInstance Modify the behavior of the instanceof keyword class MySpecialClass { static [Symbol.hasInstance](instance) { return instance === 'special'; } } console.log('special' instanceof MySpecialClass); // => true console.log('not so special' instanceof MySpecialClass); // => false

Slide 35

Slide 35 text

Symbol.match Use in place of a regular expression for String#match class Matcher { constructor(value) { this.value = value; } [Symbol.match](string) { return string.indexOf(this.value) === -1 ? null : [this.value]; } } console.log('mehfoobar'.match(/foo/)); // => [foo] console.log('mehfoobar'.match(new Matcher('foo'))); // => [foo] console.log('bazbuzzbar'.match(new Matcher('foo'))); // => null

Slide 36

Slide 36 text

Symbol.toPrimitive Overload the == operator class CarrotOrNine { [Symbol.toPrimitive](hint) { return hint === 'string' ? 'CARROT' : 9; } } let instance = new CarrotOrNine(); console.log('CARROT' == instance); // => true console.log(9 == instance); // => true

Slide 37

Slide 37 text

Symbol.iterator • Make your object or class iterable • Make your class work with for..of • Make your class work with ... (spread operator)

Slide 38

Slide 38 text

Symbol.iterator class CapedCrusader { constructor(length) { this.length = length; } [Symbol.iterator]() { let i = -1, length = this.length; return { next() { if (++i < length) return { value: 'Na', done: false }; if (i === length) return { value: 'Batman!', done: false }; return { done: true }; } }; } } let bm = new CapedCrusader(8); Array.from(bm); console.log(...bm); // => Na Na Na Na Na Na Na Na Batman!

Slide 39

Slide 39 text

Proxy (Intercession)

Slide 40

Slide 40 text

What the heck is Proxy? • A constructor that returns a Proxy object • Trap and define custom behavior for fundamental object operations • new Proxy(target, handler); • target - The object to proxy • handler - An object containing the behaviors to redefine (the traps) • Revocable Proxies constructed with Proxy.revocable let { proxy, revoke } = new Proxy.revocable(target, handler);

Slide 41

Slide 41 text

Proxy const obj = { foo: 'bar', secret: 'SUPER SECRET' }; const proxy = new Proxy(obj, { get(target, property) { if (property === 'secret') return console.log('Unauthorized. This incident has been reported'); return target[property]; } }); console.log(obj.secret); // => SUPER SECRET console.log(proxy.secret); // => Unauthorized. This incident has been reported. // => undefined console.log(proxy.foo); // => bar

Slide 42

Slide 42 text

Available Traps apply has construct isExtensible defineProperty ownKeys deleteProperty preventExtensions get set getOwnPropertyDescriptor setPrototypeOf getPrototypeOf

Slide 43

Slide 43 text

Traps map 1-1 with Reflect Reflect.apply Reflect.has Reflect.construct Reflect.isExtensible Reflect.defineProperty Reflect.ownKeys Reflect.deleteProperty Reflect.preventExtensions Reflect.get Reflect.set Reflect.getOwnPropertyDescriptor Reflect.setPrototypeOf Reflect.getPrototypeOf

Slide 44

Slide 44 text

Traps and Reflect const obj = { foo: 'bar' }; const proxy = new Proxy(obj, { set(target, property, value, receiver) { console.log(`setting ${property} to ${value}`) return Reflect.set(target, property, value, receiver); }, get(target, property, receiver) { // Just do the default behavior return Reflect.get(target, property, receiver); } }); proxy.secret = 'SUPER SECRET'; // => setting secret to SUPER SECRET console.log(obj.foo); // => bar

Slide 45

Slide 45 text

Capturing method calls const whisperHandler = { apply(target, thisArg, args) { const newArgs = [ 'And I\'m never going to dance again', ...args, 'Guilty feet have got no rhythm' ]; return Reflect.apply(target, thisArg, newArgs); } }; const whisper = new Proxy((...args) => console.log(...args), whisperHandler); whisper('User created successfully'); // => And I'm never going to dance again User created succesfully Guilty feet have got no rhythm

Slide 46

Slide 46 text

Putting it all together export const dirtyHandler = { construct(target, args) { const dirty = new Set(); const dirtyProp = Symbol.for('dirty'); return new Proxy(Reflect.construct(target, args), { set(target, propertyName, value, receiver) { target[propertyName] = value; dirty.add(propertyName); return true; }, get(target, propertyName, receiver) { if (propertyName === dirtyProp) return dirty; return Reflect.get(target, propertyName, receiver); } }); } };

Slide 47

Slide 47 text

Using dirtyHandler import { dirtyHandler } from './handlers'; class Model { constructor(...props) { props.forEach(prop => this[prop] = true); } get dirty() { return this[Symbol.for('dirty')]; } } const ProxyModel = new Proxy(Model, dirtyHandler); let model = new ProxyModel('George', 'Michael'); model.foo = 'bar'; model.George = false; console.log(...model.dirty); // => foo George

Slide 48

Slide 48 text

Lots of new shiny • Reflect (Introspection) • Symbol (Self-modification) • Proxy (Intercession) (others) • Decorators • Metadata Reflection API (proposed)

Slide 49

Slide 49 text

! Thanks "