Tests:
  • createConstructor

    x
     
    // 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()
    }
  • ClassUtils

     
    // 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()
    }
Rendered benchmark preparation results:

Suite status: <idle, ready to run>

Previous results

Experimental features:

  • Test case name Result
    createConstructor
    ClassUtils

    Fastest: N/A

    Slowest: N/A

Latest run results:
Run details: (Test run date: 4 years ago)
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
Firefox 80 on Windows
View result in a separate tab
Test name Executions per second
createConstructor 382.0 Ops/sec
ClassUtils 373.5 Ops/sec