ES6 of the Week

This Week's Episode:

metaprogramming

M-m-m-m-metaprogramming?

Metaprogramming and metadata

  • Wikipedia:
    • Metaprogramming is the writing of computer programs with the ability to treat programs as their data 
      • ex. using the eval method
    • Reflection is the ability of a computer program to examine and modify its own structure and behavior (specifically the values, meta-data, properties and functions) at runtime

Symbols

  • A brand new data type (like strings, numbers, etc)!
  • Creates a unique and immutable identifier
    • I like to think of them as object references without the object

Why Symbols?

  • Safer way to assign keys for your object
    • Helps prevent doofuses from reassigning your object's properties
    • Also good for separating the metadata of an object from its public interface
      • Symbols do not make these properties private, but they do partition them
      • Excluded from iteration, JSON.stringify, etc
    • They can also guarantee a unique value

Symbolic

const sign = Symbol(); // remember, a Symbol is just a unique identifier
const zodiac = Symbol('dragon'); // the string 'dragon' is just a message to help us humans
typeof sign === 'symbol';

let obj = {};
obj[sign] = "Scorpio";

// attempting to get using dot notation doesn't work
obj.sign // undefined
// you MUST pass in the Symbol reference using bracket notation
obj[sign] // "Scorpio"

Object.keys(obj) // [] An empty array!
for (let sym of Object.getOwnPropertySymbols(obj)) console.log(sym) // 'Symbol()'

Global Symbol Registry

// app.js
const MY_METADATA = Symbol.for('metadata'); 
// this symbol is now stored in Symbol's global registry


// elsewhere in your runtime.js
if (obj[MY_METADATA]) doSomethingWithMyMetadata(obj);
else doSomethingElse();


// you can check out the message for symbols in your global registry
Symbol.keyFor(MY_METADATA) === 'metadata' // true

Reflect

  • Reflect is a new global namespace to hold introspective methods for objects
  • Surprise: you already know what introspective methods for objects are - Object.keys, Object.hasOwnProperty, Object.getOwnPropertyNames, etc...
  • Reflect is just a namespace (like Math or Date) to hold all of those methods going forward, and it's nice to have one place to put all these, rather than having to keep front-loading regular Objects with them. Don't you agree?

Reflection

let obj = {a: 1};
Object.defineProperty(obj, 'b', {value: 2});
obj[Symbol('c')] = 3;

// the Reflect API knows about Symbols, of course
Reflect.ownKeys(obj); // ['a', 'b', Symbol(c)]

class Mirror {
  constructor (size, showsYourDeepestDesires) {
    this.size = size;
    this.showsYourDeepestDesires = showsYourDeepestDesires;
  }
}

// tap into construction meta-operation of classes/contructor functions
let erised = Reflect.construct(Mirror, ['large', true]);
erised.size; // 'large'

Proxy

  • Proxies are very similar in concept to hooks in libraries like sequelize and mongoose
  • They allow you to hook onto an object's runtime-level meta-operations (ex. get, set, has...)
    • (full list: https://babeljs.io/docs/learn-es2015/#reflect-api)
  • Note: Proxies are not well supported with Babel, due to the limitations of ES5. You must depend on compatibility with the JS engine (fortunately, Node 6 and the latest Chrome, FF and Edge browsers support it now). If you were hoping for IE support though...

Proximation

// hook into the target object's 'get' operation
let target = { foo: "Welcome, foo" }
let proxy = new Proxy(target, {
    get (receiver, name) {
        return name in receiver ? receiver[name] : throw new Error(`${name} is not a valid property!`)
    }
})
proxy.foo   === "Welcome, foo"
proxy.world === error!

List of Meta Operations

// List of meta-operations (https://babeljs.io/docs/learn-es2015/#proxies)
// Many of these you totally know!
// The rest are just a google search away!

get: ...,	// target.prop	
set: ..., 	// target.prop = value	
has: ...,	// 'prop' in target
deleteProperty: ...,	// delete target.prop
apply: ...,	// target(...args)
construct: ...,	// new target(...args)
getOwnPropertyDescriptor: ...,	// Object.getOwnPropertyDescriptor(target, 'prop')
defineProperty: ...,	// Object.defineProperty(target, 'prop', descriptor)
getPrototypeOf: ...,
// Object.getPrototypeOf(target), Reflect.getPrototypeOf(target),
// target.__proto__, object.isPrototypeOf(target), object instanceof target
setPrototypeOf: ...,	// Object.setPrototypeOf(target), Reflect.setPrototypeOf(target)
enumerate: ...,	// for (let i in target) {}
ownKeys: ...,	// Object.keys(target)
preventExtensions: ...,	// Object.preventExtensions(target)
isExtensible :...	// Object.isExtensible(target)

Metaprogramming?Like, what's the big deal, man?

With great power...

  • Good idea: Using metaprogramming to customize the behavior of your own classes at a low level
    • Ex. Building your own special collection class that extends the regular Array.prototype
  • Bad idea: Overwriting the built in behavior of Javascript objects in your runtime environment

Resources

  • ES6: http://es6-features.org/
  • Babel: https://babeljs.io/docs/learn-es2015/
  • Mozilla docs
    • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
    • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
    • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
  • Blogs
    • http://blog.keithcirkel.co.uk/metaprogramming-in-es6-symbols/
    • http://blog.keithcirkel.co.uk/metaprogramming-in-es6-part-2-reflect/
    • https://www.nczonline.net/blog/2014/04/22/creating-defensive-objects-with-es6-proxies/
    • http://soft.vub.ac.be/~tvcutsem/proxies/
    • http://www.2ality.com/2014/12/es6-proxies.html
Made with Slides.com