Test name | Executions per second |
---|---|
createConstructor | 382.0 Ops/sec |
ClassUtils | 373.5 Ops/sec |
// utility functions for Immutability and Multiple Inheritance
const createConstructor = function (funcArgs = {}) {
const { classesToInherit, classProps, defaultArgs, methods } = funcArgs
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).map(key => {
if (typeof obj[key] === 'object' && obj[key] !== null)
deepFreeze(obj[key])
})
Object.freeze(obj)
}
// the passed constructor
function InnerFunc (args = {}) {
//set object props
Object.assign(this, defaultArgs, args)
//bind the methods per object
const targetProto = Object.getPrototypeOf(this)
Object.getOwnPropertyNames(targetProto).map(key => {
if (key !== 'constructor') {
Object.defineProperty(this, key, {
enumerable: false,
value: targetProto[key] instanceof Function ?
targetProto[key].bind(this) :
targetProto[key]
})
}
})
//freeze the object
deepFreeze(this)
}
// set constructor props
Object.assign(InnerFunc, classProps)
// set inherited methods
classesToInherit.map(constr =>
Object.getOwnPropertyNames(constr.prototype).map(key => {
if (key !== 'constructor') {
InnerFunc.prototype[key] = constr.prototype[key]
}
})
)
// set manually defined methods
Object.assign(InnerFunc.prototype, methods)
// method to copy object with some props changed
InnerFunc.prototype.copyWithProps = function(props = {}) {
return new this.constructor(Object.assign({}, this, props))
}
// freeze the constructor
deepFreeze(InnerFunc)
return InnerFunc
}
// BADLY WRITTEN CLASSES
class Height {
constructor(args = {}) {
this.height = args.height
}
getHeight() { return `Height: ${this.height}` }
static heightInternal() { return 3 }
}
class Weight {
constructor(args = {}) {
this.weight = args.weight
}
getWeight() { return `Weight: ${this.weight}` }
static weightInternal() { return 3 }
}
// WELL WRITTEN CLASSES
const Person = createConstructor({
classesToInherit: [ Height, Weight ],
classProps: {
species: 'humans',
internalMethod() {
return 'Hello from inside the constructor'
}
},
defaultArgs: {
name: 'John',
age: 20,
weight: 60,
height: 160
},
methods: {
// Uncomment to replace the inherited one
//getHeight() { return 'CORRECT' },
// combines native with inherited methods
greet() {
console.log(
`Hello, ${this.name}. ` +
`${this.getWeight()}, ${this.getHeight()}`
)
},
// calculates the method name first
['get' + 'Info']() { return `Name: ${this.name}, age: ${this.age}` },
// can also use async, * generator
// which can also be static
}
})
for (let i = 0; i < 50; i++) {
mark = new Person({ name: 'Mark', weight: 90, otherArg: true })
skinnyMark = mark.copyWithProps({ age: 32, weight: 75 })
skinnyMark.greet()
mark.greet()
}
// utility functions for Immutability and Multiple Inheritance
const ClassUtils = class Self {
copyWithProps(props = {}) {
return new this.constructor(Object.assign({}, this, props))
}
finalizeInstance(props = {}) {
if (!Object.isFrozen(this)) {
//set object props
Object.assign(this, props)
//bind the methods per object
const targetProto = Object.getPrototypeOf(this)
Object.getOwnPropertyNames(targetProto).map(key => {
if (key !== 'constructor') {
Object.defineProperty(this, key, {
enumerable: false,
value: targetProto[key] instanceof Function ?
targetProto[key].bind(this) :
targetProto[key]
})
}
})
//freeze the object
Self.deepFreeze(this)
}
}
static deepFreeze(obj) {
Object.getOwnPropertyNames(obj).map(key => {
if (typeof obj[key] === 'object' && obj[key] !== null)
Self.deepFreeze(obj[key])
})
Object.freeze(obj)
}
static finalizeClass({ target, props = {}, sources = [] }) {
if (!Object.isFrozen(target)) {
// set constructor props
Object.assign(target, props)
// set prototype methods
sources.concat([Self, target]).map(constr =>
Object.getOwnPropertyNames(constr.prototype).map(key => {
if (key !== 'constructor') {
target.prototype[key] = constr.prototype[key]
}
})
)
// freeze the constructor
Self.deepFreeze(target)
}
}
}
ClassUtils.deepFreeze(ClassUtils)
// BADLY WRITTEN CLASSES
class Height {
constructor(args = {}) {
this.height = args.height
}
getHeight() { return `Height: ${this.height}` }
static heightInternal() { return 3 }
}
class Weight {
constructor(args = {}) {
this.weight = args.weight
}
getWeight() { return `Weight: ${this.weight}` }
static weightInternal() { return 3 }
}
// WELL WRITTEN CLASSES
const Person = class Self {
constructor({ name, age, weight, height } = {}) {
this.finalizeInstance({
name: name || 'John',
age: age || 20,
weight: weight || 60,
height: height || 160
})
}
// Uncomment to replace the inherited one
//getHeight() { return 'CORRECT' }
// combines native with inherited methods
greet() { console.log(`Hello, ${this.name}. ${this.getWeight()}, ${this.getHeight()}`) }
// calculates the method name first
['get' + 'Info']() { return `Name: ${this.name}, age: ${this.age}` }
// class method
static internalMethod() {
return 'Hello from inside the constructor'
}
// can also use async, * generator
// which can also be static
}
ClassUtils.finalizeClass({
target: Person,
sources: [ Height, Weight ],
props: { species: 'humans' }
})
for (let i = 0; i < 50; i++) {
mark = new Person({ name: 'Mark', weight: 90, otherArg: true })
skinnyMark = mark.copyWithProps({ age: 32, weight: 75 })
skinnyMark.greet()
mark.greet()
}