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