JS Object Creation

JavaScript performance comparison

Revision 5 of this test case created

Info

Creating custom objects with hidden state and public mutators.

Preparation code

 
<script>
Benchmark.prototype.setup = function() {
    //// player.js
   
    var PlayerNewerHotness = (function () {
        var execute = function (config, attr, tests, args) {
                var result = true;
   
                // shortcut to returning the requested value because the rest of the validations don't need to be executed
                if (args[0] === undefined && args.length === 0) {
                    return config[attr];
                }
   
                // throw an error if too many arguments were passed into the validate functino
                arguments.length !== 4 && dndError({
                    args: arguments
                    ,fn: "execute"
                });
   
                // make the arguments, passed into the original function, into an actual array
                args = ([]).slice.call(args, 0);
   
                // throw an error if too many arguments were passed into the original function
                args.length > 1 && dndError({
                    args: args
                    ,fn: attr
                });
   
                args = args[0];
                if (args || args === 0 || args === "") {
                    // run all the specified tests on the first value passed into the original function
                    while (tests.length && (result = (tests.shift())(args)));
   
                    // throw an error if the input fails any of the validations specified
                    !result && dndError({
                        args: args
                        ,fn: attr
                    });
   
                    config[attr] = args;
                }
   
                // return the object to allow chaining
                return this;
            }
   
            ,fixObject = function (str, collection) {
                var result;
   
                if (str) {
                    if (str.getType) {
                        result = str;
                    } else if (Util.isString(str) && collection.named(str)) {
                        result = collection.named(str);
                    } else {
                        dndError({
                            args: str
                            ,fn: "fixObject - " + collection
                        });
                    }
                }
   
                return result;
            }
   
            ,PlayerNewerHotness = function (config) {
                config = Util.clone(config);
   
                this.age = function (delta) {
   
                    return execute.call(this, config, "age", [Util.isNumeric, function (d) { return ~~d > 0; }], arguments);
                };
   
                this.background = function () {
                   
                    return config.background || "This character's background is unknown.";
                };
   
                this.caste = function (delta) {
                    arguments[0] = fixObject(delta, Castes);
   
                    return execute.call(this, config, "caste", [function (d) { return Util.isType(d, "Caste") }], arguments);
                };
   
                this.height = function (delta) {
   
                    return execute.call(this, config, "height", [Util.isNumeric, function (d) { return ~~d > 0; }], arguments);
                };
   
                this.job = function (delta) {
                    arguments[0] = fixObject(delta, Classes);
   
                    return execute.call(this, config, "job", [function (d) { return Util.isType(d, "Role") }], arguments);
                };
   
                this.level = function (delta) {
   
                    return execute.call(this, config, "level", [Util.isNumeric, function (d) { return ~~d > -3; }], arguments);
                };
   
                this.lineage = function () {
                   
                    return config.lineage || "No one knows where this character comes from."
                };
   
                this.name = function (delta) {
   
                    return execute.call(this, config, "name", [Util.isString], arguments);
                };
   
                this.race = function (delta) {
                    arguments[0] = fixObject(delta, Races);
   
                    return execute.call(this, config, "race", [function (d) { return Util.isType(d, "Race") }], arguments);
                };
   
                this.stats = function (delta) {
                    arguments[0] = fixObject(delta, Stats);
   
                    return execute.call(this, config, "stats", [function (d) { return Util.isType(d, "Stats") }], arguments);
                };
   
                this.title = function (delta) {
   
                    return execute.call(this, config, "title", [Util.isString], arguments);
                };
   
                this.weight = function (delta) {
   
                    return execute.call(this, config, "weight", [Util.isNumeric, function (d) { return ~~d > 0; }], arguments);
                };
   
                // for the values passed in to run through their setters so that they can be massaged into the correct form(s)
                for (var i in config) {
                    this[i](config[i]);
                }
   
                !this.isValid() && dndError({
                    args: config
                    ,fn: "Player constructor"
                    ,level: "warn"
                });
   
                config.background = this.job();
                config.lineage = this.caste();
            };
       
        PlayerNewerHotness.prototype = {
            getType: function () {
   
                return "[object Player]";
            }
   
            ,hp: function () {
   
                return (this.stats().adjustHP() + this.caste().dice) * this.level();
            }
   
            ,isValid: function () {
   
                return Util.isType(this.job(), "Role")
                    && this.level() >= 0
                    && Util.isType(this.race(), "Race")
                    && Util.isType(this.stats(), "Stats");
            }
   
            ,move: function () {
   
                return this.race().move;
            }
   
            ,skills: function () {
               
                return this.job().skills();
            }
   
            ,thaco: function () {
   
                return this.job().thaco[this.level()] + this.stats().adjustTHAC0();
            }
   
            ,toString: function () {
   
                return "Player" + (this.name() ? " - " + this.name() : "");
            }
        }
       
        return function (c) {
   
            return new PlayerNewerHotness(c || {});
        }
    }());
   
    //// player.js
   
    var PlayerOldBusted = function (config) {
        if (this === (function () {return this;}())) {
            return new Player(config);
        }
       
        var
        // mutable properties
             _age
            ,_caste         // current Caste
            ,_height
            ,_job           // Fighter, Cleric, Mage/Thief, etc.
            ,_level
            ,_name
            ,_race
            ,_stats
            ,_title         // eg. Sir, Count, Lady, etc.
            ,_weight
   
        // immutable properties
            ,_background    // initial job
            ,_gender
            ,_lineage       // begining Caste
            ,_race
   
            ,initMethods = [
                 "age"
                ,"caste"
                ,"height"
                ,"job"
                ,"level"
                ,"name"
                // ,"race" // not a setter method, cannot change race
                ,"stats"
                ,"title"
                ,"weight"
            ]
   
        // private methods
            ,invalidArgumentsError = function (fn) {
                return new Error("Invalid argument(s) passed into ." + fn + "() :: " + arguments[1]);
            }
   
            ,tooManyArgumentsError = function (fn) {
                return new Error("Too many arguments passed into ." + fn + "() :: " + arguments);
            }
   
            ,validateInputNumber = function (delta) {
                if (arguments.length === 1) {
                    if (Util.isNumeric(delta) && (delta = parseInt(delta, 10)) > 0 && delta) {
                        return delta;
                    } else {
                        throw invalidArgumentsError(this, arguments);
                    }
                } else if (arguments.length > 1) {
                    throw tooManyArgumentsError(this, arguments);
                }
   
                return false; // fallthrough fail-safe
            }
   
            ,validateInputString = function (delta) {
                if (arguments.length === 1) {
                    if (Util.isString(delta)) {
                        return delta;
                    } else {
                        throw invalidArgumentsError(this, arguments);
                    }
                } else if (arguments.length > 1) {
                    throw tooManyArgumentsError(this, arguments);
                }
   
                return false; // fallthrough fail-safe
            }
        ;
   
        this.age = function (delta) {
            if (delta = validateInputNumber.apply("age", arguments)) {
                _age = delta;
            }
   
            return _age;
        };
   
        this.caste = function (delta) {
            if (arguments.length === 1) {
                if (Util.isString(delta)) {
                    delta = Castes.named(delta);
                }
   
                if (!!delta && delta.getType && "[object Caste]" === delta.getType()) {
                    _caste = delta;
                } else {
                    throw invalidArgumentsError("caste", arguments);
                }
            } else if (arguments.length > 1) {
                throw tooManyArgumentsError("job", arguments);
            }
           
            return _caste;
        };
   
        this.height = function (delta) {
            if (delta = validateInputNumber.apply("height", arguments)) {
                _height = delta;
            }
   
            return _height;
        };
   
        this.job = function (delta) {
            if (arguments.length === 1) {
                if (delta && (delta.getType && "[object Role]" === delta.getType()) ||
                    (Util.isString(delta) && (delta = Classes.named(delta)))
                ) {
                    _job = delta;
                } else {
                    throw invalidArgumentsError("job", arguments);
                }
            } else if (arguments.length > 1) {
                throw tooManyArgumentsError("job", arguments);
            }
   
            return _job;
        };
   
        this.level = function (delta) {
            if (delta = validateInputNumber.apply("level", arguments)) {
                _level = delta;
            }
   
            return _level;
        };
   
        this.name = function (delta) {
            if (delta === "" || (delta = validateInputString.apply("name", arguments))) {
                _name = delta;
            }
   
            return _name;
        };
   
        this.stats = function (delta) {
            if (arguments.length === 1) {
                if (delta.getType && delta.getType() === "[object Stats]") {
                    _stats = delta;
                } else {
                    throw invalidArgumentsError("stats", arguments);
                }
            } else if (arguments.length > 1) {
                throw tooManyArgumentsError("stats", arguments);
            }
   
            return _stats;
        };
   
        this.title = function (delta) {
            if (delta === "" || (delta = validateInputString.apply("title", arguments))) {
                _title = delta;
            }
           
            return _title;
        };
   
        this.weight = function (delta) {
            if (delta = validateInputNumber.apply("weight", arguments)) {
                _weight = delta;
            }
   
            return _weight;
        };
   
        this.background = function () {
            if (arguments.length > 0) {
                throw tooManyArgumentsError("background");
            }
   
            return _background;
        };
   
        this.gender = function () {
            if (arguments.length > 0) {
                throw tooManyArgumentsError("gender");
            }
   
            return _gender;
        };
   
        this.lineage = function () {
            if (arguments.length > 0) {
                throw tooManyArgumentsError("heritage");
            }
   
            return _lineage;
        };
   
        this.race = function () {
            if (arguments.length > 0) {
                throw tooManyArgumentsError("race");
            }
   
            return _race;
        };
   
        for (var m in initMethods) {
            this[initMethods[m]](config[initMethods[m]]);
        }
   
        // immutable properties need to be initialized here as they do not have public set methods
        _background = _job;
       
        if (config.gender) {
            _gender = config.gender;
        }
   
        _lineage = _caste;
   
        if (config.race && (Races.named(config.race) || config.race.name)) {
            _race = Races.named(config.race) || config.race;
        }
    };
   
    PlayerOldBusted.prototype = {
        getType: function () {
   
            return "Player";
        }
   
        ,hp: function () {
   
            return (this.stats().adjustHP() + this.caste().dice) * this.level();
        }
   
        ,isValid: function () {
   
            return this.job() &&
                this.level() &&
                this.stats() &&
                this.race();
        }
   
        ,move: function () {
   
            return this.race().move;
        }
   
        ,skills: function () {
           
            return ;
        }
   
        ,thaco: function () {
   
            return this.job().thaco[this.level()] + this.stats().adjustTHAC0();
        }
   
        ,toString: function () {
   
            return "[object Player]";
        }
    };
   
};
</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
Old Busted
new PlayerOldBusted
pending…
Newer Hotness
new PlayerNewerHotness
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment