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
-
Metaprogramming is the writing of computer programs with the ability to treat programs as their data
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
ES6 of the Week - 7
By Tom Kelly
ES6 of the Week - 7
Metaprogramming
- 1,503