3

I need a true iterator that will work like this:

var haystackObj = {
        'needle': 'abc',
        'prop2': {
                'prop1': 'def',
                'prop2': {
                        'needle': 'ghi',
                },
                'needle': 'jkl',
        },
};
var needleKey = 'needle';
var iterator = {
    next: function () {
            /* 
             * WHAT CODE GOES HERE? 
             * 
             * Should return the next property, recursively, with the name 
             * equal to needleKey, of haystackObj.
             *
             */
    }
};

var value = iterator.next();
console.log(value); // -> 'abc'
value = iterator.next();
console.log(value); // -> 'ghi'
value = iterator.next();
console.log(value); // -> 'jkl'

I think this would be trivial with a for(k in o) and first-class continuations, but JS doesn't have those.

EDIT: I can only scan haystackObj once.

EDIT2: I am not looking for "a way to iterate through object properties." I am looking for an iterator of object properties. That is a huge difference. The problem is not as trivial as it may look at first glance.

Nick Zalutskiy
  • 14,952
  • 7
  • 53
  • 50
  • 1
    What happened to "def"? Why is "ghi" outputted before "jkl"? – Bergi Feb 04 '13 at 21:38
  • i guess "def" is ignored because its property name does not match needleKey `needle` – lostsource Feb 04 '13 at 21:41
  • 2
    Did you try anything? It looks like you just want SO to make the code for you... – Florian Margaine Feb 04 '13 at 21:41
  • 4
    And btw: properties order is not certified in JS. Your requirements are impossible to fulfill. – Florian Margaine Feb 04 '13 at 21:41
  • further more you can use recursion and continuation passing style. – mpm Feb 04 '13 at 21:41
  • 2
    when did JS lose `for( var key in obj )` ?? – rlemon Feb 04 '13 at 21:42
  • What do you mean by "critical path"? – Bergi Feb 04 '13 at 21:45
  • Wow people, so many negative votes. I have considered this problem for the past day. It's not as trivial as it looks! for(var key in obj) won't let me return one property at a time which is what an iterator does. – Nick Zalutskiy Feb 04 '13 at 21:46
  • Try to freaking solve this problem before you downvote. I tried for(k in o) and several other methods, non of them satisfy the requirements of what I posted. I tried to put the problem as succinctly as possible. – Nick Zalutskiy Feb 04 '13 at 21:49
  • 2
    most of you missed the point of the question didn't you. he want's "yield" semantics in JS. he want's to iterate a "tree" like structure and return control to the caller where the "key" is found, then continue iteration. – Stan R. Feb 04 '13 at 21:51
  • @Nick I really do not want to go back to conclusions... The people there were never nice to me.... – Naftali Feb 04 '13 at 21:51
  • @StanR. Then he should have used google and he would have found [this](http://stackoverflow.com/questions/4037252/how-to-simulate-javascript-yield)... – Naftali Feb 04 '13 at 21:52
  • @Neal If I had considered to search for 'yield in javascript' perhaps I would have. – Nick Zalutskiy Feb 04 '13 at 21:56
  • @Neal, SO is for contributing ideas. Maybe Nick didn't realize that there is such a thing as "yield" and hence he couldn't have Googled for it. It's SO's job to recognize this and comment with a link, but it's apparent that its a circus over here. – Stan R. Feb 04 '13 at 21:56
  • SyntaxError: Unexpected string – 1983 Feb 04 '13 at 21:57
  • @xtal Thanks, missed a comma there. – Nick Zalutskiy Feb 04 '13 at 22:00
  • @StanR. [This is relevant to you](http://meta.stackexchange.com/q/166231/155556) – Naftali Feb 04 '13 at 22:06
  • @Neal The simulated 'yield' [as linked](http://stackoverflow.com/questions/4037252/how-to-simulate-javascript-yield) won't work since the problem in this case is saving and then restoring the state of the generator. – Nick Zalutskiy Feb 04 '13 at 22:19
  • @Nick, you should always explain why you're asking what you're asking. – 1983 Feb 04 '13 at 22:22
  • @xtal I thought I did. =) In the original title I said a 'true iterator'. The question I posed asked for exactly that. And as far as I can tell atm, this problem can't be solved in JS. People just jumped to the conclusion that it was a stupid question. – Nick Zalutskiy Feb 04 '13 at 22:32
  • @xtal Added another edit that will hopefully make that clear. – Nick Zalutskiy Feb 04 '13 at 22:37
  • @Nick Yeah, but if you don't say *why* you want something, then that closes the door to the community offering alternative suggestions and perhaps even a completely different (and better) approach. – 1983 Feb 04 '13 at 22:38
  • @xtal It would be very difficult for me to explain why and it would cloud a simple question (and a known concept! or so I thought) in details that are too specific to my needs. This code grabs a property and runs an NFA simulator on it, the NFA machine match tells the iterator when to return. I just stated the essence of the problem. – Nick Zalutskiy Feb 04 '13 at 22:42
  • @Nick Your link just cites the entire Wikipedia article. You are still not being clear. For example JS already has internal iterators. – 1983 Feb 04 '13 at 22:44
  • @xtal In other words, I thought that "Can I have an iterator?" in JavaScript is a perfectly valid, self contained question... – Nick Zalutskiy Feb 04 '13 at 22:44
  • @xtal Well JS **doesn't** have iterators! That's the problem. It lets you iterate over object properties in a loop. There doesn't seem to be a way to consume properties one by one and preserve the state of that iteration. Changed the link to something more relavant. – Nick Zalutskiy Feb 04 '13 at 22:46
  • @Nick. Okay that's a better explanation! You do it inside-out in JavaScript: iterate over a structure, pass control to a function, and then return from that function when you want to resume the iteration where you left off. – 1983 Feb 04 '13 at 22:59

4 Answers4

7

Properties order is not guaranteed in JS. Different engines behave differently. (Some engines based on alphabetical order, other based on last added order.)

Your requirements are thus impossible to fulfill.

If you just wanted an iterator without minding the order, you could take a look at this question/answers: How to simulate JavaScript yield?

This is what the spec says about the properties order:

The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active enumeration. A property name must not be visited more than once in any enumeration.

In reality however, you can expect a certain order from most browsers: Elements order in a "for (… in …)" loop

The only way I see to implement a fake generator (according to the fact that the order in reality suits you) would be to copy your object, and delete the scanned properties of the copy when needed. This'd mean you wouldn't rescan twice the same properties. Some code example:

var Iterator = function() {
    var copy = $.extend(haystackObj, true);
    // ^ using jQuery's extend for a quick function, but use w/e you want.
    // Anyway keep it in a closure. This copy will have its properties deleted
    // after each iteration.

    return {
        next: function next() {
            var found = false,
                needle;
            for (var prop in copy) {
                if (typeof copy[prop] === 'object') {
                    // Since next() doesn't take any argument...
                    // That's a bad solution. You should use an inner function
                    // to recurse. But I'm going to bed right now!
                    var copyCopy = $.extend(copy, true);
                    copy = copy[prop];
                    found = next();
                    copy = copyCopy;
                }

                else {
                    if (prop === needleKey) {
                        found = true;
                    }
                }

                if (found) {
                    needle = copy[prop];
                }

                // Delete the current property to simulate a real generator.
                delete copy[prop];

                if (found) {
                    return needle;
                }
            }
        }
    };
};

// Usage:
var iterator = Iterator();
iterator.next(); // "abc"

This code doesn't work (see jsfiddle), and I'm going to sleep. But you can see where it's going and how you could make something.

Community
  • 1
  • 1
Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
1

Although Florian Margaine's answer points out that the order of the properties are dependent on the js engine, this solution works in chrome. Took me a little bit of tweaking, but here it is http://jsfiddle.net/6zCkJ/3/: Edited (this solution was done before OP said the tree can only be processed once)

var needleKey = 'needle';
var currIndex = 0;
var runningIndex = 0;
var getValueByIndex = function (obj) {
    var objToSearch = obj || haystackObj;
    for (var x in objToSearch) {
        if (x == needleKey) {

            if (runningIndex == currIndex)  {
                currIndex += 1;
                return objToSearch[x];
            }
            runningIndex += 1;
        } else if (typeof objToSearch[x] == 'object') {
            var found = getValueByIndex(objToSearch[x]);
            if (found) return found;
        }

    }
}

var iterator = {
    next: function () {
        runningIndex = 0;
        return getValueByIndex(0);
    }
};

Another approach which will only traverse the tree a single time is as follows http://jsfiddle.net/6zCkJ/6/. The catch is that you must load the values array whenever the needle is updated:

var currIndex = 0;
var valuesArray = [];

var loadValues = function (obj) {
    var objToSearch = obj || haystackObj;
    for (var x in objToSearch) {
        if (x == needleKey) {
            valuesArray.push(objToSearch[x])
        } else if (typeof objToSearch[x] == 'object') {
            loadValues(objToSearch[x]);
        }
    }
}

loadValues();
console.log(valuesArray);
var iterator = {
    next: function () {
        return valuesArray[currIndex++];
    }
};

Edit: So far all answers posted here involve having to navigate the whole tree at least once or more which is not what the OP is looking for, including having to copy the object and remove properties as they are traversed. There is a solution though which involves marking the objects as they traversed with meta data which allows you to skip over the objects the next time they are encountered. Using my first approach it would be rather trivial to add these optimizations and hopefully accomplish what the OP is requesting.

Alright, so I couldn't resist trying to get this to work. Here is how I did it http://jsfiddle.net/6zCkJ/12/ . You can see that I am storing the found objects in the foundObjects object, where the key is made up of the path to that object so you can do a quick lookup to see if it has already been recursed over. The numFound is used to increment the running index properly. I have not tested this heavily, but it should be a good start:

var Iterator = function () {
    var needleKey = 'needle';
    var currIndex = 0;
    var runningIndex = 0;
    var foundObjects = {};

    var getValueByIndex = function (obj,currentPath) {
        var objToSearch = obj || haystackObj;
        for (var x in objToSearch) {
            currentPath += x + '_';
            if (x == needleKey) {

                if (runningIndex == currIndex) {
                    currIndex += 1;
                    if (!foundObjects[currentPath]) {
                        foundObjects[currentPath] = {
                            numFound: 0,
                            finished: false
                        };
                    }
                    foundObjects[currentPath].numFound += 1;
                    return objToSearch[x];
                }
                runningIndex += 1;
            } else if (typeof objToSearch[x] == 'object') {
                if (foundObjects[currentPath] && foundObjects[currentPath].finished) {
                    runningIndex += foundObjects[currentPath].numFound;
                } else {
                    var found = getValueByIndex(objToSearch[x],currentPath);
                    if (found) {
                        return found;
                    }
                }
            }
            if (!foundObjects[currentPath]) {
                foundObjects[currentPath] = {
                    numFound: 0,
                    finished: true
                };
            }
            foundObjects[currentPath].finished = true;
        }
    }

    this.next = function () {
        runningIndex = 0;

        return getValueByIndex(0,'');
    }
};
var iterator = new Iterator();
var value = iterator.next();
Justin Bicknell
  • 4,804
  • 18
  • 26
  • 1
    Nice one. It also works on Firefox and Opera. Can't test on IE unfortunately... However, you're scanning the `objToSearch` on every iteration. Not quite what OP wants. – Florian Margaine Feb 04 '13 at 22:02
  • I was not going for performance, but you are absolutely correct – Justin Bicknell Feb 04 '13 at 22:04
  • Very nice. I would put all of this in a closure though. – Naftali Feb 04 '13 at 22:05
  • After thinking about it, this looks nothing like an generator-like. – Florian Margaine Feb 04 '13 at 22:10
  • Yes, I've considered a similar solution. But as Florian stated I can only scan the object once, because this can potentially happen very often. I would need to write an essay on why I have such specific requirements, but they are what they are. – Nick Zalutskiy Feb 04 '13 at 22:10
  • I even thought of getting a list of keys using Object.keys and then indexing into that. That would let me "jump" to the right point without having to scan up to the property every time. However, I also need the prototype chain to be resolved. Object.keys will only return hasOwn properties... – Nick Zalutskiy Feb 04 '13 at 22:12
  • @Nick - no problem, I updated my answer to include another approach. Let me know what you think – Justin Bicknell Feb 04 '13 at 22:26
  • @JustinBicknell This is actually the solution that sent me searching. The problem is that its not an iterator. You pay the price of the entire scan up front and then you can consume one result at a time. In my application the object can be very large and you may only need the first five or so occurrences. – Nick Zalutskiy Feb 04 '13 at 22:30
  • @Nick Is destructive iteration an option? As in, the original object fed to the iterator is modified every iteration. That way, you can excise all the content of the object up to your last match, which would be functionally identical to maintaining state. – Asad Saeeduddin Feb 04 '13 at 22:33
  • @Asad This is similar to a solution that Florian added to his question in an edit. I can't modify the object in any way (my code doesn't own it), but _could_ make a copy before I start destroying properties. – Nick Zalutskiy Feb 04 '13 at 22:58
  • @Asad I don't think thats necessary, unless you want it as an exercise. Florian has "almost working" code in his answer. The idea is the same. – Nick Zalutskiy Feb 04 '13 at 23:04
  • 1
    @Nick - just a thought, but I'm not going to implement this, you could take my first approach and add a list of already traversed objects, which you can skip over for the next scan (not quite the same as deleting). That way you don't need to "copy" the whole obj in advance – Justin Bicknell Feb 04 '13 at 23:54
  • 1
    @JustinBicknell I think this may be the best of both worlds so far. A sort of "fast forwarding" to the last property. You would need to keep a chain of these, since you are doing a deep search, but still doable. What I would need to measure is the cost of that for(k in o) up to first key that I'm actually interested in. I suspect copying the object is more expensive anyway... – Nick Zalutskiy Feb 04 '13 at 23:59
  • @Nick - this is very similar to a data mining technique I learned in University, can't remember the name. But essentially when you have completed traversing a subtree, you "mark" it with meta data that can be used to skip over it the next scan. In this case your meta data would have to contain the number of matches, and maybe the cost – Justin Bicknell Feb 05 '13 at 00:24
  • @Nick I've updated my answer to reflect these ideas. Please let me know if that works out. – Justin Bicknell Feb 05 '13 at 05:27
  • I can't see how you would iterate over an object starting from a specific property. Literally. The idea indeed seems to be the best of both worlds, but I just can't see how you would implement that. – Florian Margaine Feb 05 '13 at 07:19
  • @FlorianMargaine you're not starting off at a specific property, but you are pruning the tree in a sense. Because if you mark a property as "visited" then you do not have to recurse on that property again. you would effectively tag the property with meta information which you would use to answer to the parent. this is a technique commonly used in data mining algorithms – Justin Bicknell Feb 05 '13 at 07:39
  • @JustinBicknell yes, but you're still iterating over it to see if it's a "marked property" or not. You sure don't have to recurse, but the iteration takes place. – Florian Margaine Feb 05 '13 at 07:49
  • @FlorianMargaine When skip over a very deep branch you have a huge performance gain. It's pretty easy to see that. Now its my bedtime – Justin Bicknell Feb 05 '13 at 08:06
  • Indeed. Now on a practical point of view, how do you implement the marking? It's not like JS has metadata for objects like Clojure does. I'm genuinely interested. – Florian Margaine Feb 05 '13 at 18:51
  • @FlorianMargaine - You bring up a good point. Just adding a property to the object is not an option (just realized). You would have to create a lookup object where the key is identifies the object itself (perhaps stringify the tree path to that property), and the value is the meta data. Makes sense? – Justin Bicknell Feb 05 '13 at 19:02
  • So you would almost duplicate the original object, including nesting? I'm not sure the trouble is worth it :-) – Florian Margaine Feb 05 '13 at 19:05
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/23992/discussion-between-justin-bicknell-and-florian-margaine) – Justin Bicknell Feb 05 '13 at 19:06
  • @FlorianMargaine Not quite, a single level obj with key value indexes – Justin Bicknell Feb 05 '13 at 19:08
  • @FlorianMargaine Whether it's "worth it" depends on the performance gains – Justin Bicknell Feb 05 '13 at 19:17
  • 1
    Alright, after having a chat, it indeed looks like the most performant solution. I'd just go for it if my solution turns out to be the bottleneck though, since it's quite a lot of trouble to set up. – Florian Margaine Feb 05 '13 at 19:30
  • @FlorianMargaine - I was curious, so i attempted to get this to work - see my updated answer – Justin Bicknell Feb 05 '13 at 21:26
  • @Nick - updated my answer again with an attempt at the latest idea – Justin Bicknell Feb 05 '13 at 21:44
1

Assuming I understand you correctly, and bearing in mind that this is not a 'true yield', and putting all the code where you seem to want it,

var iterator = {
    next: function () {
        /* 
        * WHAT CODE GOES HERE? 
        * 
        * Should return the next property, recursively, with the name 
        * equal to needleKey, of haystackObj.
        *
        */
        var values=[], findneedles;
        findneedles = function(o){
            var k;
            for(k in o){
                if(k === needleKey){
                    values.push(o[k]);
                }else if(typeof o[k] === 'object'){
                    findneedles(o[k]);
                }
            }
        };
        findneedles(haystackObj);
        this.next = function(){
            return values.shift();
        };
        return values.shift();
    }
};
1983
  • 5,882
  • 2
  • 27
  • 39
  • The problem with this is that it is not an iterator. It scans the entire object up front, then uses those values. An iterator would only scan up to the next match and preserve state. – Asad Saeeduddin Feb 04 '13 at 22:43
  • Yes, I already said that. You may as well just recurse over the structure and pass in a callback. But in the absence of any explanation of why this not acceptable ... – 1983 Feb 04 '13 at 22:53
  • The reason this is not acceptable is because it is more computationally expensive than an iterator. Justin Bicknell has already posted an example of the approach you are taking here (see his second snippet). This does not answer the question. – Asad Saeeduddin Feb 04 '13 at 22:58
  • Yp, this is exactly the same solution as Justin Bicknell's but you shift the results, he index into the array. Check my comments to his answer. The object can be very large and the entire point of the iterator is not to pay that search price up front. – Nick Zalutskiy Feb 04 '13 at 23:01
  • Then just recurse and throw an exception when you're done. (And yeah; I hadn't seen his second answer when I wrote mine.) – 1983 Feb 04 '13 at 23:04
  • @Nick Or do a breadth-first search and store references to unsearched child objects so you can resume iteration. – 1983 Feb 04 '13 at 23:16
  • @xtal An iterator doesn't know when it's done, such is the nature of an iterator. =) As far as storing references to un-searched objects, see second part of Florians answer. I might do that. – Nick Zalutskiy Feb 04 '13 at 23:34
  • I said when *you're* done, not the iterator. If you never know if you're done then you're going to have to scan the whole object anyway. And are you saying that you'd rather copy your entire enormous object rather than just store references to only the unvisited children? – 1983 Feb 04 '13 at 23:40
  • I was referring to "throw an exception." It would be thrown from inside the iterator I presume. I'm saying the solutions are similar because to "know" what the unvisited children are you still have too loop through them (at each level of recursion.) And then store them, so you are still doing some copying. – Nick Zalutskiy Feb 05 '13 at 01:21
  • @Nick. Throwing an exception: you only iterate as much as you need. Breadth-first search: yes, you'd loop over one level at a time. I have no idea what your data is like so I can say whether this is performant or not. You have identified a bottleneck in your code and are measuring the speed of various solutions, right? – 1983 Feb 05 '13 at 08:00
0

I'm going to answer this one for posterity.

In ECMAScript 6 we have a yield statement. But lets say you're nuts and you'd like to use this feature right now. Compiled using the traceur-compiler to your plain old JavaScript, we get the following.

Input:

var iterator = function* (object) {

        for(var key in object) {
                yield key;
                for( k of iterator(object[key]) ) {
                        yield k;
                }
        }
};

var o = {
        a: 10,
        b: 11,
        c: {
                ca: 12,
                cb: 13,
        },
        d: 14,
};

var res = [];
for( key of iterator(o) ) {
        res.push(key);
}

res;

Output

var $__generatorWrap = function(generator) {
        return $traceurRuntime.addIterator({
                next: function(x) {
                        switch (generator.GState) {
                                case 1:
                                        throw new Error('"next" on executing generator');
                                case 3:
                                        throw new Error('"next" on closed generator');
                                case 0:
                                        if (x !== undefined) {
                                        throw new TypeError('Sent value to newborn generator');
                                }
                                case 2:
                                        generator.GState = 1;
                                if (generator.moveNext(x, 0)) {
                                        generator.GState = 2;
                                        return {
                                                value: generator.current,
                                                done: false
                                        };
                                }
                                generator.GState = 3;
                                return {
                                        value: generator.yieldReturn,
                                        done: true
                                };
                        }
                },
                'throw': function(x) {
                        switch (generator.GState) {
                                case 1:
                                        throw new Error('"throw" on executing generator');
                                case 3:
                                        throw new Error('"throw" on closed generator');
                                case 0:
                                        generator.GState = 3;
                                throw x;
                                case 2:
                                        generator.GState = 1;
                                if (generator.moveNext(x, 1)) {
                                        generator.GState = 2;
                                        return {
                                                value: generator.current,
                                                done: false
                                        };
                                }
                                generator.GState = 3;
                                return {
                                        value: generator.yieldReturn,
                                        done: true
                                };
                        }
                }
        });
};
var iterator = function(object) {
        var $that = this;
        var $arguments = arguments;
        var $state = 20;
        var $storedException;
        var $finallyFallThrough;
        var $__0;
        var $__1;
        var $__2;
        var $__3;
        var $__4;
        var $__5;
        var key;
        var $G = {
                GState: 0,
                current: undefined,
                yieldReturn: undefined,
                innerFunction: function($yieldSent, $yieldAction) {
                        while (true) switch ($state) {
                                case 20:
                                        $__2 = [];
                                $state = 21;
                                break;
                                case 21:
                                        $__3 = object;
                                $state = 23;
                                break;
                                case 23:
                                        for (var $__4 in $__3) $__2.push($__4);
                                $state = 25;
                                break;
                                case 25:
                                        $__5 = 0;
                                $state = 17;
                                break;
                                case 17:
                                        if ($__5 < $__2.length) {
                                        $state = 12;
                                        break;
                                } else {
                                        $state = 19;
                                        break;
                                }
                                case 11:
                                        $__5++;
                                $state = 17;
                                break;
                                case 12:
                                        key = $__2[$__5];
                                $state = 13;
                                break;
                                case 13:
                                        if (!(key in $__3)) {
                                        $state = 11;
                                        break;
                                } else {
                                        $state = 15;
                                        break;
                                }
                                case 15:
                                        this.current = key;
                                $state = 1;
                                return true;
                                case 1:
                                        if ($yieldAction == 1) {
                                        $yieldAction = 0;
                                        throw $yieldSent;
                                }
                                $state = 3;
                                break;
                                case 3:
                                        $__0 = $traceurRuntime.getIterator(iterator(object[key]));
                                $state = 7;
                                break;
                                case 7:
                                        if (!($__1 = $__0.next()).done) {
                                        $state = 8;
                                        break;
                                } else {
                                        $state = 11;
                                        break;
                                }
                                case 8:
                                        k = $__1.value;
                                $state = 9;
                                break;
                                case 9:
                                        this.current = k;
                                $state = 5;
                                return true;
                                case 5:
                                        if ($yieldAction == 1) {
                                        $yieldAction = 0;
                                        throw $yieldSent;
                                }
                                $state = 7;
                                break;
                                case 19:
                                        $state = -2;
                                case -2:
                                        return false;
                                case -3:
                                        throw $storedException;
                                default:
                                        throw "traceur compiler bug: invalid state in state machine: " + $state;
                        }
                },
                moveNext: function($yieldSent, $yieldAction) {
                        while (true) try {
                                return this.innerFunction($yieldSent, $yieldAction);
                        } catch ($caughtException) {
                                $storedException = $caughtException;
                                switch ($state) {
                                        default:
                                                this.GState = 3;
                                        $state = -2;
                                        throw $storedException;
                                }
                        }
                }
        };
        return $__generatorWrap($G);
};
var o = {
        a: 10,
        b: 11,
        c: {
                ca: 12,
                cb: 13
        },
        d: 14
};
var res = [];
for (var $__1 = $traceurRuntime.getIterator(iterator(o)), $__0; !($__0 = $__1.next()).done;) {
        key = $__0.value;
        {
                res.push(key);
        }
}
res;

So yield statement in JavaScript, possible, but highly impractical.

What I actually ended up using

Usage example:

var object = {...};
var callback = function (key, value) {

        // Do stuff...

        return traverse.CONTINUE; 
        // or return traverse.STOP if you want the iteration to stop
};

traverse(object, callback);

Implementation:

var traverse = (function () {

        var _traverse = function (object, callback) {

                var key, value, command;
                for( key in object ) {
                        value = object[key];

                        command = callback(key, value);
                        if( command === _traverse.STOP ) {
                                return _traverse.STOP;
                        }

                        command = _traverse(value, callback);
                        if( command === _traverse.STOP ) {
                                return _traverse.STOP;
                        }
                }
        };
        _traverse.CONTINUE = 1;
        _traverse.STOP = 2;

        return _traverse;
})();
Nick Zalutskiy
  • 14,952
  • 7
  • 53
  • 50