Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript Metaprogramming

JavaScript Metaprogramming

NebraskaJS, March 2016

Nick Nisi

March 22, 2016
Tweet

More Decks by Nick Nisi

Other Decks in Programming

Transcript

  1. 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
  2. Metaprogramming with eval const foo = { bar: true };

    const name = document.querySelector('#inp1').value; const value = document.querySelector('#inp2').value; eval(`foo.${name} = ${value}`);
  3. 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
  4. 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.`); });
  5. 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;
  6. 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
  7. 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
  8. Object Reflection methods The Object object in JavaScript contains several

    Reflection methods • Object.keys (introspection) • Object.entries (introspection) • Object.defineProperty (intercession) • Object.freeze (intercession)
  9. Reflect • Static object containing JavaScript Reflection methods • Contains

    some similar methods to Object, but with saner return values
  10. 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 }
  11. Reflect.apply function greeting(name) { return `${this.greeting}, ${name}!`; } const context

    = { greeting: 'Hello' }; const args = [ 'Nick' ]; Reflect.apply(greeting, context, args); // => Hello, Nick!
  12. 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');
  13. 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));
  14. 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
  15. 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
  16. 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; } // ... }
  17. 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');
  18. 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
  19. 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
  20. 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
  21. 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
  22. Symbol.iterator • Make your object or class iterable • Make

    your class work with for..of • Make your class work with ... (spread operator)
  23. 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!
  24. 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);
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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); } }); } };
  30. 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
  31. Lots of new shiny • Reflect (Introspection) • Symbol (Self-modification)

    • Proxy (Intercession) (others) • Decorators • Metadata Reflection API (proposed)