ES2015 Classes & Symbols
Alex Bularca
Web Tools Team Lead @ Everymatrix
ES2015 Classes
- Just syntactic sugar over existing OO patterns
- Formalise existing best practices as a language feature
- Remove a bunch of boilerplate
They allow you to turn this:
function inherits(child, base) {
var klass = function() {};
klass.prototype = base.prototype;
child.prototype = new klass();
}
function Shape(id, x, y) {
this.id = id;
this.x = x;
thix.y = y;
}
Shape.prototype.move = function (x, y) {
this.x = x;
this.y = y;
}
function Circle(id, x, y, radius) {
Shape.call(this, id, x, y);
this.radius = radius;
}
inherits(Circle, Shape);
Into this:
class Shape {
constructor(id, x, y) {
this.id = id;
this.x = x;
this.y = y;
}
move(x, y) {
this.x = x;
this.y = y;
}
}
class Circle extends Shape {
constructor(id, x, y, radius) {
super(id, x, y);
this.radius = radius;
}
}
Support better syntax for getters and setters
function Rectangle(id, x, y, width, height) {
Shape.call(this, id, x, y);
this.width = width;
this.height = height;
}
inherits(Rectangle, Shape);
Object.defineProperty(Rectangle.prototype, 'surface', {
get: function () {
return this.x * this.y;
}
});
class Rectangle extends Shape {
constructor(id, x, y, width, height) {
super(id, x, y);
this.width = width;
this.height = height;
}
get surface() {
return this.x * this.y;
}
}
Even more
import React from 'react';
import mixin from 'react-mixin';
import {Status} from 'react-router';
@mixin.decorate(Status)
class MyComponent extends React.Component {
constructor() {
// this.getParams() comes from the Status mixin defined by react-router
let params = this.getParams();
}
}
Symbols - reflection with implementation
- Globally unique
- Immutable
- Never conflict with string object keys
Getting our feet wet
let foo = Symbol(); // => Symbol()
let bar = Symbol('bar'); // => Symbol(bar)
Symbol('foo') == Symbol('foo'); // => false
new Symbol(); // TypeError: Symbol is not a constructor
// symbols can be used as object keys
var myObj = {
foo: 'foo',
[foo]: 'bar',
[bar]: 'baz'
};
Object.keys(myObj); // ['foo']
Object.getOwnPropertyNames(myObj); // ['foo']
Object.getOwnPropertySymbols(myObj); // [Symbol(), Symbol(bar)]
Real world usage
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn'),
};
log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');
Avoiding name clashes
var size = Symbol('size');
class Collection {
constructor() {
this[size] = 0;
}
add(item) {
this[this[size]] = item;
this[size]++;
}
static sizeOf(instance) {
return instance[size];
}
}
var x = new Collection();
assert(Collection.sizeOf(x) === 0);
x.add('foo');
assert(Collection.sizeOf(x) === 1);
assert.deepEqual(Object.keys(x), ['0']);
assert.deepEqual(Object.getOwnPropertyNames(x), ['0']);
assert.deepEqual(Object.getOwnPropertySymbols(x), [size]);
Providing a protocol
class Iterable {
constructor() {
this._data = [];
}
add(item) {
this._data.push(item);
}
*[Symbol.iterator]() {
let i = 0;
while (this._data[i] !== undefined) {
yield this._data[i++];
}
}
}
var collection = new Iterable();
collection.add('foo');
collection.add('bar');
for (let item of collection) {
console.log(item);
}
Array.prototype.map = function (callback) {
var returnValue = new Array(this.length);
this.forEach(function (item, index, array) {
returnValue[index] = callback(item, index, array);
});
return returnValue;
}
class StringCollection extends Array {
add(item) {
if (typeof item == 'string') {
this.push(item);
} else {
throw new TypeError(
'Only string values can be inserted in a StringCollection');
}
}
}
let col = new StringCollection();
col.add('foo');
col.add('bar');
let result = col.map(function (item) {
return item;
}
console.log(result instanceof StringCollection); // false
Symbol - species
Array.prototype.map = function (callback) {
var Species = this.constructor[Symbol.species];
var returnValue = new Species(this.length);
this.forEach(function (item, index, array) {
returnValue[index] = callback(item, index, array);
});
return returnValue;
}
class StringCollection extends Array {
add(item) {
if (typeof item == 'string') {
this.push(item);
} else {
throw new TypeError(
'Only string values can be inserted in a StringCollection');
}
}
}
let col = new StringCollection();
col.add('foo');
col.add('bar');
let result = col.map(function (item) {
return item;
}
console.log(result instanceof StringCollection); // true
Symbol - species
Many more built in symbols
- Symbol.instanceof
- Symbol.iterator
- Symbol.isConcatSpreadable
- Symbol.unscopables
- Symbol.match
- Symbol.replace
- Symbol.search
- Symbol.split
- Symbol.species
- Symbol.toPrimitive
- Symbol.toStringTag
References
Thank you!
Questions
Anyone?
ES2015 Classes and Symbols
By Alex Bularcă
ES2015 Classes and Symbols
- 700