5
\$\begingroup\$

This is the final iteration of an iterative review. The previous iteration can be found here: Utilities for various builtins #1

This code solves the following issue:

var i = 9
i.sqrt();     // 3
Math.sqrt(i); // 3

And other similar dilemmas. It also saves you from having to write your own mean and max function. I've also been told this is known as "syntatic sugar".

It's also worth it's weight in keypresses.

/**
 * Adds some utilities to the base classes.
 * Note that this is modular and is able to modify any class it is given.
 * @author FreezePhoenix
 * @version 1.1.0
 */

/**
 * @type {Object}
 * @constant
 */
const XtraUtils = {};
/**
 * @class
 * @classdesc Enables utilities to be added to classes.
 */
class Utility {
   /**
    * Create a new utility
    * @param {Object} ofWhat               - First part of the filter to test if it is a constructor.
    * @param {Function} ofWhat.constructor - Second part of the filter.
    * @return {Boolean}                    - false means something went wrong, true means it succeded.
    * @this Utility
    * @throws {TypeError} The first argument of Utility must be a class.
    */
    constructor(ofWhat) {
        if (typeof ofWhat == 'function' && typeof new ofWhat() === 'object') {
            this.utils = {};
            Object.defineProperty(this, 'aidsIn', {
                value: ofWhat,
                writable: false
            });
            this.Activated = [];
            this.OverWritten = {};
            return true;
        } else {
            console.error(
                'new Utility expects an object template to be passed as the first argument.'
            );
            return false;
        }
    }
    /**
     * Activates the utilities in this Utility instance.
     * @this Utility
     */
    activate() {
        for (const x in this.utils) {
            if (this.aidsIn.prototype[x]) {
                this.OverWritten[x] = this.aidsIn.prototype[x].valueOf();
            }
            this.aidsIn.prototype[x] = this.utils[x];
            this.Activated.push(x);
        }
    }
   /**
    * @this Utility
    */
    deactivate() {
        this.Activated.forEach((item, index) => {
            if (this.OverWritten[item]) {
                this.aidsIn.prototype[item] = this.OverWritten[item];
            } else {
                delete this.aidsIn.prototype[item];
            };
        });
        this.Activated = [];
    }
    /**
     * @this Utility
     */
    addUtil(name, util) {
        this.utils[name] = util;
    }
}
XtraUtils.Number = new Utility(Number);
XtraUtils.String = new Utility(String);
XtraUtils.Array = new Utility(Array);
XtraUtils.Number.addUtil('map', function(in_min, in_max, out_min, out_max) {
    return (this - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
});
XtraUtils.Number.addUtil(function(inputBase = 10, outputBase = 10) {
    parseInt(this, inputBase).toString(outputBase);
}, 'toBaseN');
XtraUtils.Number.addUtil('pow', function(n) {
    return Math.pow.apply(Math, this, n);
});
XtraUtils.Number.addUtil('ceil', function() {
    return Math.ceil.apply(Math,this);
});
XtraUtils.Number.addUtil('floor', function() {
    return Math.floor.apply(Math,this);
});
XtraUtils.Number.addUtil('sqrt', function() {
    return Math.sqrt.apply(Math,this);
});
XtraUtils.Array.addUtil('max', function() {
    return Math.max.apply(Math, this);
});
XtraUtils.Array.addUtil('min', function() {
    return Math.min.apply(Math, this);
});
XtraUtils.Array.addUtil('mean', function() {
    let total = 0;
    var isFirst = true;
    this.forEach(function(item) {
        if (typeof item === 'string') {
            throw Error('Item must be all numbers');
        }
        total += item;
    });
    const mean = total / this.length;
    return mean;
});
\$\endgroup\$
  • \$\begingroup\$ Could you describe the intent of extending and problem being solved? I did check the previous version of the question; it misses a specific description too. \$\endgroup\$ – Igor Soloydenko May 24 '18 at 19:35
  • 1
    \$\begingroup\$ @IgorSoloydenko Will do. \$\endgroup\$ – FreezePhoenix May 24 '18 at 19:37
  • \$\begingroup\$ Yes, a bit. It's still not stated directly. Do I understand correctly, that this code attempts to provide the extension methods in abstract context; as in "syntax sugar"? \$\endgroup\$ – Igor Soloydenko May 24 '18 at 19:55
  • \$\begingroup\$ @IgorSoloydenko Yes. \$\endgroup\$ – FreezePhoenix May 24 '18 at 19:58
2
+50
\$\begingroup\$

The Utility class should keep track of its own activation state, otherwise calling .activate() multiple times on the same instance (without calling .deactivate() in between) might cause some problems; also, in the current code, calling .addUtil() after .activate() leaves the instance in a limbo state, where the method name is added to this.utils but isn't actually available on the aided class's prototype, even though the instance is activated. I can imagine this leading to some frustrating debugging on a more complex project. I'd suggest that calling .addUtil() on an activated Utility instance should either immediately activate the added function, or throw an Error (this sounds more consistent with your approach).

(also, as a side note, I wouldn't call this syntactic sugar. Sugar is just simpler way of writing something. e.g. { ...obj } is sugar for Object.assign({}, obj); it doesn't actually add new functionality, which your code certainly does)

\$\endgroup\$
  • \$\begingroup\$ I've decided to do a mix of both. If override is present, then do it no matter what and instantly activate it. But otherwise, throw an error. \$\endgroup\$ – FreezePhoenix Jul 30 '18 at 19:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.