Detecting and fixing circular references in JavaScript

后端 未结 15 883
庸人自扰
庸人自扰 2020-11-28 23:29

Given I have a circular reference in a large JavaScript object

And I try JSON.stringify(problematicObject)

And the browser throws

15条回答
  •  余生分开走
    2020-11-29 00:09

    Just to throw my version into the mix... below is a remix of @dkurzaj 's code (which is itself a remix of @Aaron V 's, @user4976005 's, @Trey Mack 's and finally @Freddie Nfbnm 's [removed?] code) plus @darksinge 's WeakMap idea. So... this thread's Megamix, I guess :)

    In my version, a report (rather than console.log'ed entries) is optionally returned as an array of objects. If a report is not required, testing stops on the first sighting of a circular reference (a'la @darksinge 's code).

    Further, hasOwnProperty has been removed as Object.keys returns only hasOwnProperty properties (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys ).

    function isCyclic(x, bReturnReport) {
        var a_sKeys = [],
            a_oStack = [],
            wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
            oReturnVal = {
                found: false,
                report: []
            }
        ;
    
        //# Setup the recursive logic to locate any circular references while kicking off the initial call
        (function doIsCyclic(oTarget, sKey) {
            var a_sTargetKeys, sCurrentKey, i;
    
            //# If we've seen this oTarget before, flip our .found to true
            if (wm_oSeenObjects.has(oTarget)) {
                oReturnVal.found = true;
    
                //# If we are to bReturnReport, add the entries into our .report
                if (bReturnReport) {
                    oReturnVal.report.push({
                        instance: oTarget,
                        source: a_sKeys.slice(0, a_oStack.indexOf(oTarget) + 1).join('.'),
                        duplicate: a_sKeys.join('.') + "." + sKey
                    });
                }
            }
            //# Else if oTarget is an instanceof Object, determine the a_sTargetKeys and .set our oTarget into the wm_oSeenObjects
            else if (oTarget instanceof Object) {
                a_sTargetKeys = Object.keys(oTarget);
                wm_oSeenObjects.set(oTarget /*, undefined*/);
    
                //# If we are to bReturnReport, .push the  current level's/call's items onto our stacks
                if (bReturnReport) {
                    if (sKey) { a_sKeys.push(sKey) };
                    a_oStack.push(oTarget);
                }
    
                //# Traverse the a_sTargetKeys, pulling each into sCurrentKey as we go
                //#     NOTE: If you want all properties, even non-enumerables, see Object.getOwnPropertyNames() so there is no need to call .hasOwnProperty (per: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)
                for (i = 0; i < a_sTargetKeys.length; i++) {
                    sCurrentKey = a_sTargetKeys[i];
    
                    //# If we've already .found a circular reference and we're not bReturnReport, fall from the loop
                    if (oReturnVal.found && !bReturnReport) {
                        break;
                    }
                    //# Else if the sCurrentKey is an instanceof Object, recurse to test
                    else if (oTarget[sCurrentKey] instanceof Object) {
                        doIsCyclic(oTarget[sCurrentKey], sCurrentKey);
                    }
                }
    
                //# .delete our oTarget into the wm_oSeenObjects
                wm_oSeenObjects.delete(oTarget);
    
                //# If we are to bReturnReport, .pop the current level's/call's items off our stacks
                if (bReturnReport) {
                    if (sKey) { a_sKeys.pop() };
                    a_oStack.pop();
                }
            }
        }(x, '')); //# doIsCyclic
    
        return (bReturnReport ? oReturnVal.report : oReturnVal.found);
    }
    

提交回复
热议问题