4. “Metaprogramming is a programming technique in
which computer programs have the ability to treat
programs as their data. It means that a program can
be designed to read, generate, analyse or transform
other programs or itself while running
source: Wikipedia
5. “Metaprogramming is a programming technique in
which computer programs have the ability to treat
programs as their data. It means that a program can
be designed to read, generate, analyse or transform
other programs or itself while running
source: Wikipedia
7. “In computer science,reflection is the ability of a
computer program to examine,introspect, and modify
its own structure and behaviour at runtime. Reflection
is a valuable language feature and it facilitates
metaprogramming.
source: Wikipedia
8. Language features that enables
reflection
1.
Evaluate a string as if it
were a source code
statement at runtime.
2.
Convert a string matching
the symbolic name of a class
or function into a reference
to or invocation of that class
or function.
3.
Discover and modify source
code constructions (such as
code blocks, classes,
methods, protocols, etc.) as
first-class objects at
runtime.
source: Wikipedia
15. ➔ The seventh type of values in JavaScript
Symbol
➔ The Symbol() function returns a value of type symbol
➔ Symbol() always returns unique values
➔ They may be used as an identifier for object properties
source: MDN
16. Example: Using Symbol as property identifier
const nameField = Symbol('beer name');
const beer = {
[nameField]: 'VB!'
};
console.log(beer[nameField]); // -> VB!
17. Example: Using Symbol as property identifier
const nameField = Symbol('beer name');
const beer = {
[nameField]: 'VB!'
};
console.log(beer[nameField]); // -> VB!
18. Example: Using Symbol as property identifier
const nameField = Symbol('beer name');
const beer = {
[nameField]: 'VB!'
};
console.log(beer[nameField]); // -> VB!
19. Example: Using Symbol as property identifier
const nameField = Symbol('beer name');
const beer = {
[nameField]: 'VB!'
};
console.log(beer[nameField]); // -> VB!
20. Well-known
Symbols
➔ A set of built-in JavaScript symbols
➔ Represent internal language behaviours
➔ They were not exposed to developers before
source: MDN
21. Symbols are used by ES6 to enable
“Reflection within implementation”.
Developers include them on their existing
classes and objects to change the default
behaviour of an operation or action.
source: keithcirkel.co.uk
22. What can be done using well-known symbols
➔ Control the behaviour of instanceof for an implemented class
➔ Manipulate the behaviour of for of when iterated over a class
➔ Control over Array.concat
➔ Custom matches for String.match(), String.replace(),
String.search() and String.split()
➔ Control the behaviour of Javascript when converting objects
to primitive types
Check MDN for a list of well-known symbols
23. Example: Using well-known Symbol.hasInstance to modify behaviour of “instanceof”
class MyArray {
}
const friends = ['foo', 'bar'];
console.log(friends instanceof MyArray); // -> false
class MyArray {
static [Symbol.hasInstance](object) {
return Array.isArray(object);
}
}
const friends = ['foo', 'bar'];
console.log(friends instanceof MyArray); // -> true
24. Example: Using well-known Symbol.toPrimitive for a ShoppingBasket class
class ShoppingBasket {
constructor() {
this.items = [];
}
add(title, quantity, price) {
this.items.push({ title, quantity, price});
}
total() {
return this.items.reduce(function(accumulator, item) {
return accumulator + (item.price * item.quantity);
}, 0);
}
}
}
const basket = new ShoppingBasket();
basket.add('bread', 2, 2.5);
basket.add('milk', 1, 1.4);
28. Proxy, as the name indicates, provides
“Reflection through interception”.
It wraps objects and intercepts their
behaviours through traps.
source: keithcirkel.co.uk
29. What can be done using Proxy
➔ A trap for delete operator
➔ Manipulate the behaviour of in operator
➔ Control over getting and setting property values
➔ A trap for function calls
➔ A trap for new operator
Check MDN for a list of Proxy features
30. Example: Implementation of a virtual field using Proxy
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
get: function(object, fieldName) {
if (fieldName === 'fullName') {
return `${object.firstName} ${object.lastName}`;
}
return object[fieldName];
}
};
const proxiedStudent = new Proxy(student, studentProxy);
console.log(proxiedStudent.fullName); // -> Jackson Rowe
31. Example: Implementation of a virtual field using Proxy
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
get: function(object, fieldName) {
if (fieldName === 'fullName') {
return `${object.firstName} ${object.lastName}`;
}
return object[fieldName];
}
};
const proxiedStudent = new Proxy(student, studentProxy);
console.log(proxiedStudent.fullName); // -> Jackson Rowe
32. Example: Implementation of a virtual field using Proxy
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
get: function(object, fieldName) {
if (fieldName === 'fullName') {
return `${object.firstName} ${object.lastName}`;
}
return object[fieldName];
}
};
const proxiedStudent = new Proxy(student, studentProxy);
console.log(proxiedStudent.fullName); // -> Jackson Rowe
33. Example: Implementation of a virtual field using Proxy
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
get: function(object, fieldName) {
if (fieldName === 'fullName') {
return `${object.firstName} ${object.lastName}`;
}
return object[fieldName];
}
};
const proxiedStudent = new Proxy(student, studentProxy);
console.log(proxiedStudent.fullName); // -> Jackson Rowe
34. Example: Value validation using Proxy
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
set: function(object, fieldName, value) {
// validation logic for the “age” field
if (fieldName === 'age') {
if (typeof value !== 'number') {
throw TypeError('only numbers are accepted');
}
if (value <= 0) throw TypeError('that is impossible');
}
object[fieldName] = value;
return true;
}
};
const proxiedStudent = new Proxy(student, studentProxy);
proxiedStudent.age = 'a'; // errors
proxiedStudent.age = -1; // errors
proxiedStudent.age = 12; // does not error
35. Example: Value validation using Proxy
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
set: function(object, fieldName, value) {
// validation logic for the “age” field
if (fieldName === 'age') {
if (typeof value !== 'number') {
throw TypeError('only numbers are accepted');
}
if (value <= 0) throw TypeError('that is impossible');
}
object[fieldName] = value;
return true;
}
};
const proxiedStudent = new Proxy(student, studentProxy);
proxiedStudent.age = 'a'; // errors
proxiedStudent.age = -1; // errors
proxiedStudent.age = 12; // does not error
36. Example: Value validation using Proxy
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
set: function(object, fieldName, value) {
// validation logic for the “age” field
if (fieldName === 'age') {
if (typeof value !== 'number') {
throw TypeError('only numbers are accepted');
}
if (value <= 0) throw TypeError('that is impossible');
}
object[fieldName] = value;
return true;
}
};
const proxiedStudent = new Proxy(student, studentProxy);
proxiedStudent.age = 'a'; // errors
proxiedStudent.age = -1; // errors
proxiedStudent.age = 12; // does not error
37. Example: Value validation using Proxy
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
set: function(object, fieldName, value) {
// validation logic for the “age” field
if (fieldName === 'age') {
if (typeof value !== 'number') {
throw TypeError('only numbers are accepted');
}
if (value <= 0) throw TypeError('that is impossible');
}
object[fieldName] = value;
return true;
}
};
const proxiedStudent = new Proxy(student, studentProxy);
proxiedStudent.age = 'a'; // errors
proxiedStudent.age = -1; // errors
proxiedStudent.age = 12; // does not error
38. Example: Protect “id” field from deletion using a Proxy
const student = {
id: 'jackson-rowe',
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
deleteProperty: function(object, fieldName) {
if (fieldName === 'id') return false;
delete object[fieldName];
return true;
}
};
const proxiedStudent = new Proxy(student, studentProxy);
delete proxiedStudent.id // -> false
console.log(proxiedStudent.id); // jackson-rowe
39. Example: Protect “id” field from deletion using a Proxy
const student = {
id: 'jackson-rowe',
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
deleteProperty: function(object, fieldName) {
if (fieldName === 'id') return false;
delete object[fieldName];
return true;
}
};
const proxiedStudent = new Proxy(student, studentProxy);
delete proxiedStudent.id // -> false
console.log(proxiedStudent.id); // jackson-rowe
40. Example: Protect “id” field from deletion using a Proxy
const student = {
id: 'jackson-rowe',
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
deleteProperty: function(object, fieldName) {
if (fieldName === 'id') return false;
delete object[fieldName];
return true;
}
};
const proxiedStudent = new Proxy(student, studentProxy);
delete proxiedStudent.id // -> false
console.log(proxiedStudent.id); // jackson-rowe
41. Example: Protect “id” field from deletion using a Proxy
const student = {
id: 'jackson-rowe',
firstName: 'Jackson',
lastName: 'Rowe',
};
const studentProxy = {
deleteProperty: function(object, fieldName) {
if (fieldName === 'id') return false;
delete object[fieldName];
return true;
}
};
const proxiedStudent = new Proxy(student, studentProxy);
delete proxiedStudent.id // -> false
console.log(proxiedStudent.id); // jackson-rowe
43. Reflect is all about “Reflection through
introspection” - provides API to discover very
low level information about code.
source: keithcirkel.co.uk
44. What can be done using Reflect
➔ Call a function using Reflect.apply()
➔ Define a property using Reflect.defineProperty()
➔ Delete a property using Control over Reflect.deleteProperty()
➔ Get a property value using Reflect.get()
➔ Set property value using Reflect.set()
➔ Check if a property exist using Reflect.has()
Check MDN for a list of Reflect features
45. What Reflect object offers are either a
newer versions of existing methods or
entirely new methods - allowing new levels
of Reflection within JavaScript.
source: keithcirkel.co.uk
46. Example: Deleting a property using Reflect.deleteProperty()
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
console.log(student.firstName); // -> Jackson
Reflect.deleteProperty(student, 'firstName');
console.log(student.firstName); // -> undefined
47. Example: Deleting a property using Reflect.deleteProperty()
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
console.log(student.firstName); // -> Jackson
Reflect.deleteProperty(student, 'firstName');
console.log(student.firstName); // -> undefined
48. Example: Deleting a property using Reflect.deleteProperty()
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
console.log(student.firstName); // -> Jackson
Reflect.deleteProperty(student, 'firstName');
console.log(student.firstName); // -> undefined
49. Example: Deleting a property using Reflect.deleteProperty()
const student = {
firstName: 'Jackson',
lastName: 'Rowe',
};
console.log(student.firstName); // -> Jackson
Reflect.deleteProperty(student, 'firstName');
console.log(student.firstName); // -> undefined
51. Resources
● Wikipedia - Metaprogramming
● Wikipedia - Reflection in Computer Science
● Mozilla - Metaprogramming
● Metaprogramming in ES6 by Keith Cirkel
52. Credits
Special thanks to all the people who made and
released these awesome resources for free:
▷ MDN web docs
▷ Wikipedia and the contributors
▷ Keith Cirkel for the Metaprogramming series
▷ Presentation template by SlidesCarnival