isCircularObject vs isCircular vs isCircularES5

JavaScript performance comparison

Revision 5 of this test case created by Xotic750 and last updated

Preparation code

<script>
/*!
 * utility v0.3.0
 * http://code.google.com/p/utility-js/
 *
 * Developed by:
 * - Xotic750
 *
 * GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
 */


/*jslint sub: true, maxerr: 50, maxlen: 250, indent: 4 */
/*global window,document,console,alert,jQuery,GM_getValue,GM_setValue,GM_deleteValue,GM_listValues,localStorage,sessionStorage,rison,Components */

(function utility_closure() {
    "use strict";

    ///////////////////////////
    //       Variables
    ///////////////////////////

    var core = {};

    ///////////////////////////
    //       Utility
    ///////////////////////////

    function Utility(obj) {
        Object.defineProperty(this, "refersTo", {
            enumerable: true,
            value: obj
        });

        return this;
    }

    ///////////////////////////
    //       utility
    ///////////////////////////

    function utility(obj) {
        switch (utility.typeOf(obj)) {
        case "object":
            if (utility.isPlainObject(obj)) {
                Utility.prototype = core.Object.prototype;
            } else {
                Utility.prototype = core.Common.prototype;
            }

            break;
        case "array":
            Utility.prototype = core.Array.prototype;
            break;
        case "function":
            Utility.prototype = core.Function.prototype;
            break;
        case "string":
            Utility.prototype = core.String.prototype;
            break;
        case "number":
            Utility.prototype = core.Number.prototype;
            break;
        case "boolean":
            Utility.prototype = core.Boolean.prototype;
            break;
        case "date":
            Utility.prototype = core.Date.prototype;
            break;
        case "regexp":
            Utility.prototype = core.RegExp.prototype;
            break;
        case "arguments":
            Utility.prototype = core.Arguments.prototype;
            break;
        case "error":
            Utility.prototype = core.Error.prototype;
            break;
        default:
            Utility.prototype = core.Common.prototype;
        }

        return new Utility(obj);
    }

    Object.defineProperty(utility, "version", {
        value: "0.3.0"
    });

    Object.defineProperty(utility, "inheriting", {
        value: {}
    });

    Object.defineProperty(utility, "mixin", {
        value: function (obj, inheritFrom, keys) {
            keys.forEach(function (key) {
                Object.defineProperty(obj, key, Object.getOwnPropertyDescriptor(inheritFrom, key));
            });
        }
    });

    Object.defineProperty(utility, "mergein", {
        value: function (obj, inheritFrom) {
            var a = ["constructor", "prototype", "hasOwnProperty", "propertyIsEnum", "isPrototypeOf", "valueOf", "toString", "toLocaleString", "callee", "caller", "__defineGetter__", "__lookupGetter__", "__defineSetter__", "__lookupSetter__"],
                src;

            Object.getOwnPropertyNames(inheritFrom).forEach(function (key) {
                src = Object.getOwnPropertyDescriptor(inheritFrom, key);

                if (a.indexOf(key) < 0) {
                    if (utility.isFunction(src.value)) {
                        Object.defineProperty(obj, key, {
                            value: function () {
                                return inheritFrom[key].apply(this.refersTo, arguments);
                            }
                        });
                    } else if (utility.isUndefined(src.value) || (utility.isUndefined(src.get) && !utility.isUndefined(src.set))) {
                        Object.defineProperty(obj, key, {
                            get: function () {
                                return this.refersTo[key];
                            }
                        });
                    } else if (utility.isUndefined(src.get) && utility.isUndefined(src.set)) {
                        Object.defineProperty(obj, key, {
                            get: function () {
                                return this.refersTo[key];
                            },
                            set: function (val) {
                                this.refersTo[key] = val;
                            }
                        });
                    } else if (!utility.isUndefined(src.get) && utility.isUndefined(src.set)) {
                        Object.defineProperty(obj, key, {
                            set: function (val) {
                                this.refersTo[key] = val;
                            }
                        });
                    } else {
                        throw new Error("Did not find any expected property descripter elements in " + key);
                    }
                }
            });
        }
    });

    Object.defineProperty(utility, "isUndefined", {
        value: (function () {
            var u;

            return function (obj) {
                return obj === u;
            };
        }())
    });

    Object.defineProperty(utility, "isNull", {
        value: (function () {
            var n = null;

            return function (obj) {
                return obj === n;
            };
        }())
    });

    Object.defineProperty(utility, "isInitialised", {
        value: (function () {
            var u,
                n = null;

            return function (obj) {
                return !(obj === u || obj === n);
            };
        }())
    });

    Object.defineProperty(utility, "classStringToType", {
        value: (function () {
            var a = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Arguments", "Error", "Object"],
                o = {},
                s = Object.prototype.toString,
                key,
                val,
                prop;

            a.forEach(function (type) {
                key = "[object " + type + "]";
                val = type.toLowerCase();
                o[key] = val;
                prop = "is" + type;

                if (type === "Array" && o[s.call(Array.isArray)] === "function") {
                    Object.defineProperty(utility, prop, {
                        value: Array.isArray
                    });
                } else {
                    Object.defineProperty(utility, prop, {
                        value: function (obj) {
                            return utility.typeOf(obj) === val;
                        }
                    });
                }
            });

            return o;
        }())
    });

    Object.defineProperty(utility, "toClassString", {
        value: (function () {
            var s = Object.prototype.toString;

            return function (obj) {
                return s.call(obj);
            };
        }())
    });

    Object.defineProperty(utility, "typeOf", {
        value: (function () {
            var s = utility.classStringToType;

            return function (obj) {
                if (!utility.isInitialised(obj)) {
                    return String(obj);
                }

                return s[utility.toClassString(obj)] || typeof obj;
            };
        }())
    });

    Object.defineProperty(utility, "isNumeric", {
        value: (function () {
            var rx = /^-/;

            return function (obj) {
                return (utility.isNumber(obj) || (utility.isString(obj) && obj.length > 0)) && !isNaN(parseFloat(obj)) && isFinite(obj.toString().replace(rx, ''));
            };
        }())
    });

    Object.defineProperty(utility, "isInt", {
        value: function (obj) {
            return utility.isNumeric(obj) && parseInt(obj, 10) === parseFloat(obj);
        }
    });

    Object.defineProperty(utility, "isCircular", {
        value: function isCircular(obj, a) {
            if (!utility.isObject(obj) && !utility.isFunction(obj)) {
                return false;
            }

            if (!utility.isArray(a)) {
                if (utility.isInitialised(a)) {
                    throw new TypeError("Expected attribute to be an array");
                }

                a = [];
            }

            a.push(obj);
            var val,
                c = Object.keys(obj).some(function (key) {
                    val = obj[key];

                    return (utility.isObject(val) || utility.isFunction(val)) && (a.indexOf(val) >= 0 || isCircular(val, a));
                });

            a.pop();
            return c;
        }
    });

    Object.defineProperty(utility, "isNativeFunction", {
        value: (function () {
            var e = "{ [native code] }",
                m = e.length,
                s = String.prototype.slice;

            return function (fn) {
                return utility.isFunction(fn) && s.call(fn, -m) === e;
            };
        }())
    });

    Object.defineProperty(utility, "has", {
        value: function (obj, prop) {
            return utility.isInitialised(obj) && utility.isString(prop) && prop.length > 0 && prop in obj;
        }
    });

    Object.defineProperty(utility, "hasOwn", {
        value: (function () {
            var h = Object.prototype.hasOwnProperty;

            return function (obj, prop) {
                return utility.isInitialised(obj) && utility.isString(prop) && prop.length > 0 && h.call(obj, prop);
            };
        }())
    });

    Object.defineProperty(utility, "isDom", {
        value: (function () {
            var p = "[object HTML",
                l = p.length,
                a = ["Element", "Attr", "Text", "CDATASection", "EntityReference", "Entity", "ProcessingInstruction", "Comment", "Document", "DocumentType", "DocumentFragment", "Notation"],
                o = {},
                s = String.prototype.slice;

            a.forEach(function (type) {
                o["[object " + type + "]"] = true;
            });

            return function (obj) {
                var classString = utility.toClassString(obj);

                return utility.isObject(obj) && (o[classString] || s.call(classString, 0, l) === p);

            };
        }())
    });

    Object.defineProperty(utility, "isDocument", {
        value: function (obj) {
            return utility.toClassString(obj) === "[object HTMLDocument]";
        }
    });

    Object.defineProperty(utility, "isWindow", {
        value: function (obj) {
            var classString = utility.toClassString(obj);

            return classString === "[object Window]" || classString === "[object global]";
        }
    });

    Object.defineProperty(utility, "isPlainObject", {
        value: function (obj) {
            // Must be an Object.
            // Because of IE, we also have to check the presence of the constructor property.
            // Make sure that DOM nodes and window objects don't pass through, as well
            if (!utility.isObject(obj) || utility.isWindow(obj) || utility.isDom(obj)) {
                return false;
            }

            try {
                // Not own constructor property must be Object
                if (utility.isFunction(obj.constructor) && !utility.hasOwn(obj, "constructor") && !utility.hasOwn(obj.constructor.prototype, "isPrototypeOf")) {
                    return false;
                }
            } catch (e) {
                // IE8,9 Will throw exceptions on certain host objects #9897
                return false;
            }

            // Own properties are enumerated firstly, so to speed up,
            // if last one is own, then all properties are own.
            var key;

            for (key in obj) {}

            return utility.isUndefined(key) || utility.hasOwn(obj, key);
        }
    });

    Object.defineProperty(utility, "isEmptyObject", {
        value: function (obj) {
            if (!utility.isPlainObject(obj)) {
                throw new TypeError("Cannot call method 'isEmptyObject' of " + utility.toClassString(obj));
            }

            var name;

            for (name in obj) {
                return false;
            }

            return true;
        }
    });

    Object.defineProperty(utility, "lengthOf", {
        value: function (obj) {
            if (utility.isArray(obj) || utility.isString(obj) || utility.isArguments(obj) || ((utility.isObject(obj) || utility.isFunction(obj)) && utility.hasOwn(obj, "length"))) {
                return obj.length;
            }

            if (utility.isPlainObject(obj)) {
                return Object.keys(obj).length;
            }

            if (utility.isDom(obj)) {
                return obj.childNodes.length;
            }

            throw new Error("Cannot call method 'lengthOf' of " + utility.toClassString(obj));
        }
    });

    Object.defineProperty(utility, "hasContent", {
        value: function (obj) {
            if (!utility.isInitialised(obj)) {
                return false;
            }

            if (utility.isFunction(obj) || utility.isBoolean(obj) || utility.isWindow(obj)) {
                return true;
            }

            if (utility.isNumber(obj) || utility.isDate(obj)) {
                return isFinite(obj);
            }

            if (utility.isRegExp(obj)) {
                return utility.isString(obj.source) && obj.source.length > 0 && obj.source !== "(?:)";
            }

            if (utility.isError(obj)) {
                return utility.isString(obj.stack) && obj.stack.length > 0;
            }

            try {
                return utility.lengthOf(obj) > 0;
            } catch (e) {
                throw new Error("Cannot call method 'hasContent' of " + utility.toClassString(obj));
            }
        }
    });

    Object.defineProperty(utility, "setContent", {
        value: function (obj, val) {
            if (utility.hasContent(obj)) {
                return obj;
            }

            return val;
        }
    });

    Object.defineProperty(utility, "hasIndexOf", {
        value: function (obj, val) {
            return ((utility.isArray(obj) || !utility.isString(obj)) && obj.length > 0 && obj.indexOf(val) >= 0) || (utility.isPlainObject(obj) && Object.keys(obj).indexOf(val) >= 0);
        }
    });

    Object.defineProperty(utility, "addEvent", {
        value: function addEvent(obj, type, fn) {
            if (!utility.isDom(obj) || (!utility.has(obj, "attachEvent") && !utility.has(obj, "addEventListener"))) {
                throw new TypeError("Cannot call method 'addEvent' of " + utility.toClassString(obj));
            }

            if (!utility.isString(obj) || obj.length < 1) {
                throw new TypeError("Attribute 'type' is not a valid identifier");
            }

            if (!utility.isFunction(obj)) {
                throw new TypeError("Attribute 'fn' is not a function");
            }

            if (utility.has(obj, "attachEvent")) {
                obj['e' + type + fn] = fn;
                obj[type + fn] = function () {
                    obj['e' + type + fn](window.event);
                };

                obj.attachEvent('on' + type, obj[type + fn]);
            } else {
                obj.addEventListener(type, fn, false);
            }
        }
    });

    Object.defineProperty(utility, "removeEvent", {
        value: function (obj, type, fn) {
            if (!utility.isDom(obj) || (!utility.has(obj, "detachEvent") && !utility.has(obj, "removeEventListener"))) {
                throw new TypeError("Cannot call method 'removeEvent' of " + utility.toClassString(obj));
            }

            if (!utility.isString(obj) || obj.length < 1) {
                throw new TypeError("Attribute 'type' is not a valid identifier");
            }

            if (!utility.isFunction(obj)) {
                throw new TypeError("Attribute 'fn' is not a function");
            }

            if (utility.has(obj, "detachEvent")) {
                obj.detachEvent('on' + type, obj[type + fn]);
                obj[type + fn] = null;
            } else if (obj.removeEventListener) {
                obj.removeEventListener(type, fn, false);
            }
        }
    });

    Object.defineProperty(utility, "escapeRegExp", {
        value: (function () {
            var a = ['.', '*', '+', '?', '^', '=', '!', ':', '$', '{', '}', '(', ')', '|', '[', ']', '/', '\\'],
                rx = new RegExp("([\\" + a.join('\\') + "])", "gm");

            return function (str) {
                if (!utility.isString(str)) {
                    throw new TypeError("Attribute 'str' is not a string");
                }

                if (str.length === 0) {
                    return str;
                }

                return str.replace(rx, '\\$1');
            };
        }())
    });

    ///////////////////////////
    //       Mixin functions
    ///////////////////////////

    function mixinCommon(obj) {
        var a = ["ref", "toString", "toLocaleString", "valueOf"];

        utility.mixin(obj.prototype, core.Common.prototype, a);
    }

    ///////////////////////////
    //       Common
    ///////////////////////////

    Object.defineProperty(core, "Common", {
        value: function UCommon() {}
    });

    Object.defineProperty(core.Common.prototype, "ref", {
        get: function () {
            return this.refersTo;
        }
    });

    Object.defineProperty(core.Common.prototype, "valueOf", {
        value: function () {
            if (!utility.isInitialised(this.refersTo)) {
                return this.refersTo;
            }

            return this.refersTo.valueOf();
        }
    });

    Object.defineProperty(core.Common.prototype, "toString", {
        value: function () {
            if (!utility.isInitialised(this.refersTo)) {
                return String(this.refersTo);
            }

            return this.refersTo.toString();
        }
    });

    Object.defineProperty(core.Common.prototype, "toLocaleString", {
        value: function () {
            if (!utility.isInitialised(this.refersTo)) {
                return String(this.refersTo);
            }

            return this.refersTo.toLocaleString();
        }
    });

    ///////////////////////////
    //       Object
    ///////////////////////////

    Object.defineProperty(core, "Object", {
        value: function UObject() {}
    });

    mixinCommon(core.Object);
    utility.mergein(core.Object.prototype, Object.prototype);

    ///////////////////////////
    //       Array
    ///////////////////////////


    Object.defineProperty(core, "Array", {
        value: function UArray() {}
    });

    mixinCommon(core.Array);
    utility.mergein(core.Array.prototype, Array.prototype);

    ///////////////////////////
    //       function
    ///////////////////////////

    Object.defineProperty(core, "Function", {
        value: function UFunction() {}
    });

    mixinCommon(core.Function);
    utility.mergein(core.Function.prototype, Function.prototype);

    ///////////////////////////
    //       String
    ///////////////////////////

    Object.defineProperty(core, "String", {
        value: function UString() {}
    });

    mixinCommon(core.String);
    utility.mergein(core.String.prototype, String.prototype);

    ///////////////////////////
    //       Number
    ///////////////////////////

    Object.defineProperty(core, "Number", {
        value: function UNumber() {}
    });

    mixinCommon(core.Number);
    utility.mergein(core.Number.prototype, Number.prototype);

    ///////////////////////////
    //       Date
    ///////////////////////////

    Object.defineProperty(core, "Date", {
        value: function UDate() {}
    });

    mixinCommon(core.Date);
    utility.mergein(core.Date.prototype, Date.prototype);

    ///////////////////////////
    //       RegExp
    ///////////////////////////

    Object.defineProperty(core, "RegExp", {
        value: function URegExp() {}
    });

    mixinCommon(core.RegExp);
    utility.mergein(core.RegExp.prototype, RegExp.prototype);

    ///////////////////////////
    //       Arguments
    ///////////////////////////

    Object.defineProperty(core, "Arguments", {
        value: function UArguments() {}
    });

    mixinCommon(core.Arguments);
    utility.mergein(core.Arguments.prototype, arguments);

    ///////////////////////////
    //       Error
    ///////////////////////////

    Object.defineProperty(core, "Error", {
        value: function UError() {}
    });

    mixinCommon(core.Error);
    utility.mergein(core.Error.prototype, Error.prototype);

    ///////////////////////////
    //       Boolean
    ///////////////////////////

    Object.defineProperty(core, "Boolean", {
        value: function UBoolean() {}
    });

    mixinCommon(core.Boolean);
    utility.mergein(core.Boolean.prototype, Boolean.prototype);

    ///////////////////////////
    //       Expose
    ///////////////////////////

    window.$u = utility;
}());

function isCircular(obj, arr) {
    "use strict";

    var type = typeof obj,
        propName,
        thisVal,
        iterArr,
        lastArr;

    if (type !== "object" && type !== "function") {
        return false;
    }

    if (Object.prototype.toString.call(arr) !== '[object Array]') {
        type = typeof arr; // jslint sake
        if (!(type === "undefined" || type === null)) {
            throw new TypeError("Expected attribute to be an array");
        }

        arr = [];
    }

    arr.push(obj);
    lastArr = arr.length - 1;

    for (propName in obj) {
        thisVal = obj[propName];
        type = typeof thisVal;

        if (type === "object" || type === "function") {
            for (iterArr = lastArr; iterArr >= 0; iterArr -= 1) {
                if (thisVal === arr[iterArr]) {
                    return true;
                }
            }

            if (isCircular(thisVal, arr)) {
                return true;
            }

        }
    }

    arr.pop(obj);
    return false;
}

function isCircularES5Hybrid1(obj, arr) {
    "use strict";

    var type = typeof obj,
        propName,
        keys,
        thisVal,
        iterKeys;


    if (type !== "object" && type !== "function") {
        return false;
    }

    if (!Array.isArray(arr)) {
        type = typeof arr; // jslint sake
        if (!(type === "undefined" || type === null)) {
            throw new TypeError("Expected attribute to be an array");
        }

        arr = [];
    }

    arr.push(obj);

    for (propName in obj) {
        thisVal = obj[propName];
        type = typeof thisVal;

        if (type === "object" || type === "function") {
            if (arr.indexOf(obj[propName]) >= 0) {
                return true;
            }

            if (isCircular(thisVal, arr)) {
                return true;
            }

        }
    }

    arr.pop(obj);
    return false;
}

function isCircularES5(obj, arr) {
    "use strict";

    var type = typeof obj,
        propName,
        keys,
        thisVal,
        iterKeys;


    if (type !== "object" && type !== "function") {
        return false;
    }

    if (!Array.isArray(arr)) {
        type = typeof arr; // jslint sake
        if (!(type === "undefined" || type === null)) {
            throw new TypeError("Expected attribute to be an array");
        }

        arr = [];
    }

    arr.push(obj);

    keys = Object.keys(obj);
    for (iterKeys = keys.length - 1; iterKeys >= 0; iterKeys -= 1) {
        propName = keys[iterKeys];
        thisVal = obj[keys[iterKeys]];
        type = typeof thisVal;

        if (type === "object" || type === "function") {
            if (arr.indexOf(obj[propName]) >= 0) {
                return true;
            }

            if (isCircular(thisVal, arr)) {
                return true;
            }

        }
    }

    arr.pop(obj);
    return false;
}

function isCircularObject(node, parents) {
    parents = parents || [];

    if (!node || typeof node != "object") {
        return false;
    }

    var keys = Object.keys(node), i, value;

    parents.push(node); // add self to current path
    for (i = keys.length-1; i >= 0; i--) {
        value = node[keys[i]];
        if (value && typeof value == "object") {
            if (parents.indexOf(value) >= 0) {
                // circularity detected!
                return true;
            }

            // check child nodes
            if (arguments.callee(value, parents)) {
                return true;
            }

        }
    }

    parents.pop(node);
    return false;
}
</script>
<script>
Benchmark.prototype.setup = function() {
    var testObject = Object;
};
</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
isCircularObject
isCircularObject(testObject);
pending…
isCircular
isCircular(testObject);
pending…
isCircularES5
isCircularES5(testObject);
pending…
isCircularES5Hybrid1
isCircularES5Hybrid1(testObject);
pending…
$u.isCircular
$u.isCircular(testObject);
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