在JavaScript中实现对象属性的屈服迭代器

在JavaScript中实现对象属性的屈服迭代器,javascript,continuations,Javascript,Continuations,我需要一个true迭代器,其工作方式如下: var haystackObj = { 'needle': 'abc', 'prop2': { 'prop1': 'def', 'prop2': { 'needle': 'ghi', }, 'needle': 'jkl', }, }

我需要一个true迭代器,其工作方式如下:

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'
我认为这对于for(k-in-o)和一级连续体来说是微不足道的,但是JS没有这些

编辑:我只能扫描haystackObj一次


EDIT2:我不是在寻找“一种遍历对象属性的方法”。我是在寻找一种对象属性的方法这是一个巨大的差异。这个问题并不像乍看起来那么简单。

属性顺序在JS中是无法保证的。不同的发动机表现不同。(某些引擎基于字母顺序,其他引擎基于上次添加的顺序。)

因此,您的要求无法满足

如果您只是想要一个迭代器而不考虑顺序,您可以查看以下问题/答案:

以下是有关属性顺序的内容:

未指定枚举属性的机制和顺序(第一个算法中的步骤6.a,第二个算法中的步骤7.a)。枚举期间可能会删除正在枚举的对象的属性。如果删除了枚举期间尚未访问的属性,则不会访问该属性。如果在枚举期间向要枚举的对象添加新属性,则不保证在活动枚举中访问新添加的属性。在任何枚举中,属性名称的访问次数不得超过一次

但实际上,大多数浏览器都有一定的顺序:

我认为实现伪生成器的唯一方法(根据实际顺序适合您的事实)是复制对象,并在需要时删除副本的扫描属性。这意味着您不会对相同的属性重新扫描两次。一些代码示例:

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"

这个代码不起作用(请参阅),我要睡觉了。但是你可以看到它的发展方向以及你如何制作一些东西。

虽然Florian Margaine的回答指出属性的顺序取决于js引擎,但这个解决方案在chrome中有效。我做了一点调整,但这里是: 已编辑(此解决方案是在OP表示树只能处理一次之前完成的)

另一种只遍历树一次的方法如下。问题是,每当指针更新时,必须加载值数组:

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++];
    }
};
编辑:到目前为止,这里发布的所有答案都涉及到必须至少一次或多次浏览整个树,而这不是OP所寻找的,包括必须复制对象并在遍历属性时删除属性。不过,有一种解决方案,它涉及到在对象遍历时使用元数据标记对象,这允许您在下次遇到对象时跳过对象。使用我的第一种方法,添加这些优化并希望完成OP所要求的任务是相当简单的

好吧,所以我忍不住要让它发挥作用。我是这样做的。您可以看到,我正在将找到的对象存储在foundObjects对象中,其中键由指向该对象的路径组成,因此您可以进行快速查找,查看它是否已被递归。numFound用于正确增加运行索引。我没有对此进行过大量测试,但这应该是一个良好的开端:

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();

假设我正确地理解您,并记住这不是“真正的收益”,并将所有代码放在您想要的地方

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();
    }
};

我要为子孙后代回答这个问题

在ECMAScript 6中,我们有一个语句。但是假设你是个疯子,你现在就想使用这个功能。使用简单的旧JavaScript编译,我们得到

输入: 输出 实施:
“def”怎么了?为什么“ghi”在“jkl”之前输出?你试过什么吗?看起来您只是想为自己编写代码……顺便说一句:properties order在JS中没有经过认证。您的需求无法满足。JS什么时候丢失了(obj中的var键)?大多数人都没有抓住问题的关键,不是吗。他想要JS中的“屈服”语义。他希望迭代一个类似“树”的结构,并将控制权返回给找到“键”的调用者,然后继续迭代。你赢了游戏。祝贺回答得很好。这在12.6.4节中有详细说明。这是目前为止唯一有效的答案,为什么我应该为一个完全有效的问题投反对票,我不知道。唯一半相关的答案,似乎这里很多人根本不知道发电机这样的东西存在。顺便说一句,在我问这个问题之前,我看了一下说明书,得出了相同的结论。我还看了Object.key和friends。所以我问,万一我错过了JS的一些角落和缝隙。嗯,不错。它也适用于Firefox和Opera。很遗憾,无法在IE上测试。。。但是,每次迭代都要扫描
objToSearch
。不完全是OP想要的。我不是为了表演,但你绝对是correct@Nick-只是一个想法,但我不打算实现这一点,您可以采用我的第一种方法,添加一个已遍历对象的列表,您可以跳过该列表进行下一次扫描(与删除不完全相同)。这样你就不需要“复制”整个obj了advance@JustinBicknell我认为这可能是迄今为止两个世界中最好的。一种到最后一个属性的“快进”。由于您正在进行深入的搜索,但仍然可行,因此您需要保留一个链。我需要衡量的是(k in o)到我真正感兴趣的第一个键的成本。我怀疑复制对象的成本更高……好吧,在聊天之后,它看起来确实是最有效的解决方案。不过,如果我的解决方案成为瓶颈,我会选择它,因为设置起来相当麻烦。问题是它不是迭代器。它先扫描整个对象,然后使用这些值
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();
    }
};
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;
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;
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);
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;
})();