Slide 1

Slide 1 text

Metaprogramming in ES2015 @kidwm JSDC 2016 1

Slide 2

Slide 2 text

2 Meta- programming

Slide 3

Slide 3 text

Metaprogramming The art of 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 ” 3

Slide 4

Slide 4 text

Metaprogramming 1. Program transformation • Babel 2. Dynamic execution of expressions that contain programming commands, often composed from strings. • eval() 3. Expose the internals of the run-time engine to the programming code through application programming interfaces 4

Slide 5

Slide 5 text

Introspection The ability of a program to examine the type or properties of an object at runtime Wikipedia ” 5

Slide 6

Slide 6 text

Reflection The ability of a computer program to examine, introspect, and modify its own structure and behavior at runtime. Wikipedia ” 6

Slide 7

Slide 7 text

Reflection in JavaScript • Operators: typeof , instanceof , and delete • Function# name and Function# length • Function# bind , Function# call , and Function# apply • All of the available methods on Object are Reflection 7

Slide 8

Slide 8 text

8 Symbols

Slide 9

Slide 9 text

Symbol() // prints "Symbol()" to the console 'symbol' // TypeError: Symbol is not a constructor Symbols New primitive type in ECMAScript console.log(Symbol()); assert(typeof Symbol() === ) new Symbol(); 9

Slide 10

Slide 10 text

Symbol('foo') notEqual // prints "Symbol(foo)" toString() Symbols 1. Constants representing concepts 2. Unique property keys assert. (Symbol('foo'), Symbol('foo')); console.log(Symbol('foo')); assert(Symbol('foo'). === 'Symbol(foo)'); 10

Slide 11

Slide 11 text

Boolean(sym) === !!sym Object(sym) Object.keys(sym) Symbols are not coercible to primitives const sym = Symbol('desc'); const str1 = '' + sym; // TypeError const str2 = `${sym}`; // TypeError const str3 = +Symbol() // TypeError const str4 = String(sym); // 'Symbol(desc)' const str5 = sym.toString(); // 'Symbol(desc)' ; ; ; 11

Slide 12

Slide 12 text

Symbol.for('foo') Global Symbols registry var myObj = {}; var fooSym = ; var otherSym = Symbol.for('foo'); myObj[fooSym] = 'baz'; myObj[otherSym] = 'bing'; assert(fooSym === otherSym); assert(myObj[fooSym] === 'bing' && myObj[otherSym] === 'bing'); 12

Slide 13

Slide 13 text

notEqual iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo') Global Symbols registry is cross-realm iframe = document.createElement('iframe'); iframe.src = String(window.location); document.body.appendChild(iframe); assert. (iframe.contentWindow.Symbol, Symbol); assert( ); 13

Slide 14

Slide 14 text

Symbol.keyFor Symbols registry query var localFooSymbol = Symbol('foo'); var globalFooSymbol = Symbol.for('foo'); assert( (localFooSymbol) === undefined); assert(Symbol.keyFor(globalFooSymbol) === 'foo'); assert(Symbol.for(Symbol.keyFor(globalFooSymbol)) === Symbol.for('foo')); 14

Slide 15

Slide 15 text

Symbols as keys of properties 1. Symbols cannot be read using existing reflection tools before ES2015 • Object.getOwnPropertySymbols() 2. Symbols are not private • Object.getOwnSymbols() , Reflect.ownKeys(obj) 3. Enumerable Symbols can be copied to other objects • Object.assign(newObject, objectWithSymbols) 15

Slide 16

Slide 16 text

// foo1 and foo2 could both be `Symbol()` Symbols used as unique property keys var foo1 = Symbol('foo'); var foo2 = Symbol('foo'); var object = { [foo1]: 1, [foo2]: 2, }; assert(object[foo1] === 1 && object[foo2] === 2); 16

Slide 17

Slide 17 text

Symbols will never conflict with Object string keys Public properties usually have string keys, but for non-public properties in inheritance hierarchies, accidental name clashes can become a problem. Against unauthorized access, use WeakMap() for private data in classes. Symbols having unique identities makes them ideal as keys of meta-level properties 17

Slide 18

Slide 18 text

Symbol.iterator Symbol.iterator const obj = { data: [ 'hello', 'world' ], [ ]() { const self = this; let index = 0; return { next() { return index < self.data.length ? { value: self.data[index++] } : { done: true }; }};}}; for (const x of obj) { console.log(x);} 18

Slide 19

Slide 19 text

Symbol.hasInstance Symbol.hasInstance class MyClass { static [ ](lho) { return Array.isArray(lho); } } assert([] instanceof MyClass); 19

Slide 20

Slide 20 text

Symbol.toPrimitive Symbol.toPrimitive class Answer { [ ](hint) { if (hint === 'string') { return 'fourtytwo';} else if (hint === 'number') { return 42;} else { return 42;}}} var answer = new Answer(); +answer === 42; Number(answer) === 42; ''+answer === 'fourtytwo'; String(answer) === 'fourtytwo'; 20

Slide 21

Slide 21 text

Symbol.toStringTag Symbol.toStringTag default string description of an object: ‘[object ‘+obj[Symbol.toStringTag]+’]’ class Collection { get [ ]() { return 'Collection';} } var x = new Collection(); Object.prototype.toString.call(x) === '[object Collection]' 21

Slide 22

Slide 22 text

Symbol.species Symbol.species determine the type for derived objects, default return constructor of "this". class Foo extends Array {get [ ]() {return this}} class Bar extends Array {get [Symbol.species]() {return Array}} assert(new Foo().map(function(){}) instanceof Foo); assert(new Bar().map(function(){}) instanceof Bar); assert(new Bar().map(function(){}) instanceof Array); 22

Slide 23

Slide 23 text

Symbol.species Promises use a variant of the species pattern for static methods such as Promise.all(iterable), to return Promise from it’s prototype methods class TimeoutPromise extends Promise { static get [Symbol.species]() { return Promise; } } 23

Slide 24

Slide 24 text

Symbol.isConcatSpreadable Symbol.isConcatSpreadable class Collection extends Array { get [ ]() { return false;} } var array = [3, 4]; var collection = new Collection(5, 6); var spreadableTest = [1,2].concat(array).concat(collection); assert.deepEqual(spreadableTest, [1, 2, 3, 4, ]); 24

Slide 25

Slide 25 text

Symbol.search String Symbols • Symbol.match • Symbol.replace • Symbol.search • Symbol.split class MySearch { constructor(value) { this.value = value; } [ ](string) { return string.indexOf(this.value);}} 'foobar'.search(new MySearch('foo')); // 0 25

Slide 26

Slide 26 text

26 Reflect

Slide 27

Slide 27 text

Reflect new global Object that provides introspection methods • Newer versions of common existing Object/Function methods // Object.keys, Object.getOwnPropertyNames, Function.prototype.apply, etc • Different return values: Boolean instead of modified object • Operators as functions ESlint has a prefer-reflect rule 27

Slide 28

Slide 28 text

Reflect.construct(target, arguments, newTarget=target) Reflect.construct() The new operator as a function : Object the optional parameter newTarget points to the constructor that started the current chain of constructor calls. // new.target 28

Slide 29

Slide 29 text

Reflect.construct Reflect.construct() // ES5 style factory: function greetingFactory(name) { var instance = Object.create(Greeting.prototype); Greeting.call(instance, name); return instance; } // ES2015: const greetingFactory = (name) => (Greeting, [name]); 29

Slide 30

Slide 30 text

Reflect.deleteProperty(target, propertyKey) Reflect.deleteProperty() The delete operator as a function : boolean The delete operator also “works” for non-object references(i.e. variables). And throws TypeError in strict mode if you try to delete a non-configurable own property. 30

Slide 31

Slide 31 text

Reflect.get(target, propertyKey, receiver=target) Reflect.get() : any // Non-objects throws TypeError Reflect.get(1, 'foo'); // throws TypeError Reflect.get(false, 'foo'); // throws TypeError // These old styles don't throw assert(1['foo'] === undefined); assert(false['foo'] === undefined); 31

Slide 32

Slide 32 text

Reflect.get Reflect.get Reflect.get() The optional parameter receiver is needed when get reaches a getter later in the prototype chain. Then it provides the value for this . var myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; },} assert( (myObject, 'baz') === 3); var myReceiverObject = { foo: 4, bar: 4,}; assert( (myObject, 'baz', myReceiverObject) === 8); 32

Slide 33

Slide 33 text

Reflect.has(target, propertyKey) Reflect.has Reflect.has() The in operator as a function. : boolean myObject = {foo: 1,}; Object.setPrototypeOf(myObject, { bar: 2, get baz() { return 3;}, }); assert( (myObject, 'baz') === true); 33

Slide 34

Slide 34 text

Reflect.set(target, propKey, value, receiver=target) Reflect.set Reflect.set() : boolean Like Reflect.get(), throw on non-objects, and receiver argument which acts as the this value if target[propKey] is a setter function. var myObject = { foo: 1, set bar(v) { return this.foo = v;}} var myReceiverObject = {foo: 0}; assert( (myObject, 'bar', 1, myReceiverObject)); assert(myReceiverObject.foo === 1); 34

Slide 35

Slide 35 text

Reflect.apply(target, thisArgument, argumentsList) Reflect.apply Reflect.apply() Same as Function.prototype.apply(), but shorter : any const func = () => {}; func.apply = () => {throw new Error('Aha got you!');} func.apply(thisArg, argArray) // have own 'apply' property Function.prototype.apply.call(func, thisArg, argArray) // safe (func, thisArg, argArray); // shorter than above 35

Slide 36

Slide 36 text

Reflect.defineProperty(target, propertyKey, propDesc) Reflect.defineProperty Reflect.defineProperty() Similar to Object.defineProperty (), will throw a TypeError for invalid targets. : boolean Reflect.defineProperty(1, 'foo') // Throw TypeError console.log( ({}, 'foo', { value: 123, writable: false, configurable: false })); // true 36

Slide 37

Slide 37 text

Reflect.getOwnPropertyDescriptor(target, propertyKey) Reflect.getOwnPropertyDescriptor Reflect.getOwnPropertyDescriptor() Same as Object.getOwnPropertyDescriptor(), throws for invalid target : PropDesc | undefined Reflect.getOwnPropertyDescriptor(1, 'foo') // throws TypeError var obj = {}; Object.defineProperty(obj, 'hidden', { value: true, enumerable: false, }); var desc = (obj, 'hidden'); 37

Slide 38

Slide 38 text

Reflect.getPrototypeOf(target) Reflect.getPrototypeOf Reflect.getPrototypeOf() Same as Object.getPrototypeOf(), throws for invalid target : Object | Null var myObj = new FancyThing(); assert( (myObj) === FancyThing.prototype); Object.getPrototypeOf(1); // undefined Reflect.getPrototypeOf(1); // TypeError 38

Slide 39

Slide 39 text

Reflect.setPrototypeOf(target, proto) Reflect.setPrototypeOf Reflect.setPrototypeOf() Similar with Object.setPrototypeOf(), throw TypeError with non-object : boolean var myObj = new FancyThing(); assert( (myObj, Others.prototype) === true); assert(Reflect.getPrototypeOf(myObj) === Others.prototype); Object.freeze(myObj); assert(Reflect.setPrototypeOf(myObj) === false); 39

Slide 40

Slide 40 text

Reflect.isExtensible(target) Reflect.isExtensible Reflect.isExtensible() Replacement of Object.isExtensible(), throw TypeError with non-object : boolean var obj = {}; var NonExtensible = Object.preventExtensions({}); assert( (obj) === true); assert(Reflect.isExtensible(NonExtensible) === false); Reflect.isExtensible(1); // throws TypeError Reflect.isExtensible(false); // throws TypeError 40

Slide 41

Slide 41 text

Reflect.preventExtensions(target) Reflect.preventExtensions Reflect.preventExtensions() Replacement of Object.preventExtensions(), throw TypeError with non-object : boolean var myObject = {}; var ExtendedObj = magicalExtension({}); assert( (myObject) === true); assert(Reflect.preventExtensions(ExtendedObj) === false); Reflect.preventExtensions(1); // throws TypeError Reflect.preventExtensions(false); // throws TypeError 41

Slide 42

Slide 42 text

Reflect.ownKeys(target) Reflect.ownKeys Reflect.ownKeys() Object.getOwnPropertyNames() and Object.getOwnPropertySymbols() combine, returns all own property keys (strings and symbols) in an Array. : Array var myObject = { foo: 1, bar: 2, [Symbol.for('baz')]: 3,}; assert.deepEqual( (myObject), ['foo', 'bar', Symbol.for('baz')] ); 42

Slide 43

Slide 43 text

43 Proxies

Slide 44

Slide 44 text

new Proxy(target, handler) Proxy new global constructor to intercept and customize operations performed on objects, wraps the target with handler hooks const proxy = ; If the handler doesn’t intercept an operation then it is performed on the target. That is, it acts as a fallback for the handler. In a way, the proxy wraps the target, so have an impact on performance . 44

Slide 45

Slide 45 text

new Proxy Proxy Intercession var myObject = {}; var proxiedMyObject = (myObject, {/*handler hooks*/}); assert(myObject !== proxiedMyObject); myObject.foo = true; assert(proxiedMyObject.foo === true); proxiedMyObject.bar = true; assert(myObject.bar === true); 45

Slide 46

Slide 46 text

apply construct ownKeys get set has defineProperty deleteProperty getOwnPropertyDescriptor setPrototypeOf getPrototypeOf isExtensible preventExtensions Proxy Handler Hooks const proxy = new Proxy({}, { : Reflect.apply, : Reflect.construct, : Reflect.ownKeys, : Reflect.get, : Reflect.set, : Reflect.has, : Reflect.defineProperty, : Reflect : Reflect.getOwnPropertyDescriptor, : Reflect.setPrototypeOf, : Reflect : Reflect.isExtensible, : Reflect.preventExtensions,}); 46

Slide 47

Slide 47 text

new Proxy Proxies as prototypes const proto = ({}, { get(target, propertyKey, receiver) { console.log('GET '+propertyKey); return target[propertyKey]; }}); const obj = Object.create(proto); obj.bla; // Output: GET bla 47

Slide 48

Slide 48 text

new Proxy Access negative Array indices by Proxy ⋯ function createArray(...elements) { const handler = { get(target, propKey, receiver) { const index = Number(propKey); if (index < 0) propKey = String(target.length + index); return Reflect.get(target, propKey, receiver); }}; const target = []; target.push(...elements); return (target, handler); } 48

Slide 49

Slide 49 text

⋯Access negative Array indices by Proxy const arr = createArray('a', 'b', 'c'); console.log(arr[-1]); // c In the same way, you can use Proxy to exapnd the capability and make composition of other primitives like Map or Set 49

Slide 50

Slide 50 text

has: () => true return proxy return proxy Using Proxy to make chainable API ⋯ function urlBuilder(domain) { var parts = []; var proxy = new Proxy(function () { var returnValue = domain + '/' + parts.join('/'); parts = []; return returnValue; }, { , get: (object, prop) => { parts.push(prop); ;}, }); } 50

Slide 51

Slide 51 text

⋯Using Proxy to make chainable API Accessing a restful web service var api = urlBuilder('http://api.example.com'); assert(api.products.toys.dolls() === 'http://api.example.com/products/toys/dolls') Use this same pattern to make a tree traversal fluent API like: myDomIsh.div.span.i().textContent 51

Slide 52

Slide 52 text

Reflect.has Reflect.get Using Proxy to implement a hook of "method missing" ⋯ function Foo() { return new Proxy(this, { get: function (object, property) { return (object, property) ? (object, property) : () => console.log('Not existed' + property + ' property!'); }});} 52

Slide 53

Slide 53 text

⋯Using Proxy to implement a hook of "method missing" Foo.prototype.bar = () => console.log('you called bar.'); foo = new Foo(); foo.bar(); //=> you called bar. foo.this_method_does_not_exist(); // Not existed this_method_does_not_exist property!' 53

Slide 54

Slide 54 text

Using Proxy to put parameters into function name ⋯ const bc = new Proxy({}, { get: (object, methodName) => { var parts = methodName.match(/base(\d+)toBase(\d+)/); var from = parts && parts[1]; var to = parts && parts[2]; if (!parts || from > 36 || to > 36 || from < 2 || to < 2) throw new Error(methodName + ' is not a function'); return (string) => parseInt(string, from).toString(to); }}); 54

Slide 55

Slide 55 text

⋯Using Proxy to put parameters into function name bc.base16toBase2('deadbeef') === '11011110101011011011111011101111'; bc.base2toBase16('11011110101011011011111011101111') === 'deadbeef'; This pattern could be also used for dynamic finder: Users.find_by_first_name_and_last_name('Keith', 'David'); 55

Slide 56

Slide 56 text

Using Proxy to Implement deprecated Object.observe • use handlers: set , preventExtensions , deleteProperty and defineProperty • Object.observe could mutate an object, where as Proxy has to return a new one • the unobserve function of Proxy is not a global • proxy-object-observe on npm 56

Slide 57

Slide 57 text

Recap 1. Symbols are all about Reflection within implementation 2. Reflect is all about Reflection through introspection 3. Proxy is all about Reflection through intercession 4. Not yet as advanced, No Operator Overloading 5. Useful for Tooling or Testing 57

Slide 58

Slide 58 text

Thanks! @kidwm Ref: • Metaprogramming in ES6 by Keith Cirkel • Exploring ES6 by Dr. Axel Rauschmayer 58