1

我想根据某些条件从列表中过滤掉某些对象:

var list = [
    { mode: 1, type: 'foo' }, 
    { mode: 2, type: 'foo' }, 
    { mode: 3, type: 'foo' }, // remove this item
    { mode: 1, type: 'bar' }, 
    { mode: 2, type: 'bar' },
    { mode: 3, type: 'bar' }
];

var blah = $.grep(list, function(x){

    if(x.type === 'foo')
        return x.mode !== 3;
    else
        return true;
});

console.log('result 1:', blah);

http://jsfiddle.net/65LeJ/

这正如预期的那样工作。

我的问题是我有一个过滤器,它从我的服务器作为 JSON 发送:

var blah = list;

var filter = [
    { propertyName: 'mode', value: 3 }, 
    { propertyName: 'type', value: 'foo' }
];

$.each(filter, function(k, filterRow){

    blah = $.grep(blah, function(listItem){
        return listItem[filterRow.propertyName] === filterRow.value;
    });

});

http://jsfiddle.net/65LeJ/1/

这显然会过滤掉太多,但这只是为了让您了解它是如何工作的。

我想知道的是您对过滤器应该如何过滤掉与我的第一个示例中相同数量的行的建议。

不,我不想传递表达式并使用eval().

如果有任何不清楚的地方,请告诉我。

4

6 回答 6

3

你最好使用[].filterand [].every

这是我的解决方案:

var list = [
    { mode: 1, type: 'foo' }, 
    { mode: 2, type: 'foo' }, 
    { mode: 3, type: 'foo' }, // remove this item
    { mode: 1, type: 'bar' }, 
    { mode: 2, type: 'bar' },
    { mode: 3, type: 'bar' }
];

var blah = list;

var filter = [{ propertyName: 'mode', value: 3 }, { propertyName: 'type', value: 'foo' }];

var filtered = list.filter(function(listItem) {
    return !filter.every(function(filterItem) {
        return listItem[filterItem.propertyName] === filterItem.value;
    });
});

console.log(filtered);

http://jsfiddle.net/Dogbert/65LeJ/2/

于 2013-07-03T10:05:14.393 回答
2

如果你想保留 $.each !!!

var list = [
    { mode: 1, type: 'foo' }, 
    { mode: 2, type: 'foo' }, 
    { mode: 3, type: 'foo' }, // remove this item
    { mode: 1, type: 'bar' }, 
    { mode: 2, type: 'bar' },
    { mode: 3, type: 'bar' }
];

var filter = [
    { propertyName: 'mode', value: 3 }, 
    { propertyName: 'type', value: 'foo' }
];

var blah = $.grep(list, function(x){
    var result = true;    

    $.each(filter, function(index, element) {
        result &= (x[element.propertyName] === element.value)
    });

    return !result;
});

console.log('result 1:', blah);
于 2013-07-03T10:23:10.037 回答
1

如果您不介意使用 Function 构造函数与 Crockford 的“Eval is Evil”发生冲突,另一种方法是这样的:

var filter = [
    {propertyName: 'mode', value: 3}, 
    {propertyName: 'type', value: 'foo'}
];

var makeGrepFn = function(filter) {
    return new Function("listItem", (function() {
        var body = [], idx = -1;
        while (++idx < filter.length) {
            body.push("if ('' + listItem['" + 
                    filter[idx].propertyName + "'] !== '" + 
                    filter[idx].value + "') {return true;}");
        }
        body.push("return false;");
        return "    " + body.join("\n    ");
    }()));
};

var grepFn = makeGrepFn(filter);

var filtered = $.grep(list, grepFn);

这有各种限制。它比较字符串和数字是等价的。虽然这可以修复,但它会使代码变得非常复杂。它不适用于更复杂的对象,仅适用于具有原始属性的对象,但它仍然是一种有点有趣的方法。

grepFn最终将与此等效:

function (listItem) {
    if ('' + listItem['mode'] !== '3') {return true;}
    if ('' + listItem['type'] !== 'foo') {return true;}
    return false;
}

显然,如果您愿意,您可以使用 withArray.prototype.filter而不是这样做jQuery.grep。您只需将上面的trues 和falses 颠倒过来。

于 2013-07-10T12:50:21.557 回答
1

正如 Dogbert 所说filterevery完成了这项工作。但是,这是一种非常缓慢的方法。这就是 SQL 查询很有帮助的事情。假设您在 SQL 中有下表:

      List
+------+------+
| Mode | Type |
+------+------+
|    1 |  foo |
+------+------+
|    2 |  foo |
+------+------+
|    3 |  foo |
+------+------+
|    1 |  bar |
+------+------+
|    2 |  bar |
+------+------+
|    3 |  bar |
+------+------+

您可以执行以下两个查询之一来获取您想要的数据:

查询 1:建设性(创建一个新表)

SELECT * FROM List WHERE Mode != '3' AND Type != 'foo';

查询 2:破坏性(从表中删除行)

DELETE FROM List WHERE Mode = '3' AND Type = 'foo';

当然,JavaScript 不是 SQL。但是,我们可以使用这两个 SQL 查询作为指导,在 JavaScript 中创建两个生成正确结果的函数。

功能一:建设性

function filter(table, conditions) {
    var rows = table.length, length = conditions.length, result = [];

    for (var i = 0; i < rows;) {
        var row = table[i++];

        for (var j = 0; j < length;) {
            var condition = conditions[j++];

            if (row[condition.propertyName] !== condition.value) {
                result.push(row);
                break;
            }
        }
    }

    return result;
}

功能2:破坏性

function remove(table, conditions) {
    var rows = table.length, length = conditions.length, result = [];

    loop: for (var i = 0; i < rows;) {
        var row = table[i++];

        for (var j = 0; j < length;) {
            var condition = conditions[j++];
            if (row[condition.propertyName] !== condition.value)
                continue loop;
        }

        table.splice(--i, 1);
        rows--;
    }
}

现在给定一个表格和一个条件列表,您可以选择其中一个filterremove不想要的行。

考虑您有下表:

var table = [
    { mode: 1, type: 'foo' }, 
    { mode: 2, type: 'foo' }, 
    { mode: 3, type: 'foo' }, // remove this item
    { mode: 1, type: 'bar' }, 
    { mode: 2, type: 'bar' },
    { mode: 3, type: 'bar' }
];

鉴于以下条件:

var conditions = [
    { propertyName: 'mode', value: 3 }, 
    { propertyName: 'type', value: 'foo' }
];

你可以这样做:

var result = filter(table, conditions);

或者你可以这样做:

remove(table, conditions);

就我个人而言,我更喜欢filterremove以防万一您需要原始表格)。


好的,那么这些方法比 Dogbert 的解决方案更好吗?答案是因为它们要快得多。他们这么快的原因是:

  1. 您不使用[].filterwhich is slow 因为您为表中的每一行执行回调。
  2. 您不使用[].everywhich 也很慢,因为您为每个条件执行回调。

本质上两者filterremove都是相同的 Dogbert 解决方案。但是,由于它们是手工制作的并且不使用回调,因此执行速度要快得多。亲自查看结果:

http://jsperf.com/filter-and-every-vs-filter-vs-remove

于 2013-07-13T04:31:49.017 回答
1
var list = [
    { mode: 1, type: 'foo' }, 
    { mode: 2, type: 'foo' }, 
    { mode: 3, type: 'foo' }, 
    { mode: 1, type: 'bar' }, 
    { mode: 2, type: 'bar', name: 'Agnes'}, // remove this item
    { mode: 3}
];

var filter = [
    { propertyName: 'mode', value: 2 }, 
    { propertyName: 'type', value: 'bar' },
    { propertyName: 'name', value: 'Agnes' }
];

var filter_obj = {};
var i;

for(i=0; i<filter.length; ++i) {
  var obj = filter[i];
  filter_obj[obj.propertyName] = obj.value;
}

var results = [];
var filter_len = Object.keys(filter_obj).length;

NEXT_OBJECT:
for(i=0; i<list.length; ++i) {
  obj = list[i];

  if(Object.keys(obj).length != filter_len) {
    results.push(obj);
  }
  else {
    for(key in obj) {
      if(obj[key] != filter_obj[key]) {
        results.push(obj);
        continue NEXT_OBJECT;
      }
    }
  }
}

console.log(results);

--output:--
[Object { mode=1, type="foo"}, 
 Object { mode=2, type="foo"}, 
 Object { mode=3, type="foo"}, 
 Object { mode=1, type="bar"}, 
 Object { mode=3}]
于 2013-07-03T11:27:44.340 回答
1

如果不能使用andArray之类的方法,但是有,那么你可以试试这个:.filter.everyjQuery

var filtered = $.grep(list, function(el) {
  return $.grep(filter, function(filterEl) {
    return el[filterEl.propertyName] === filterEl.value;
  }).length !== 2;
});

这是针对您的问题的非常具体的解决方案,但比其他答案更容易推理。

于 2013-07-13T15:47:15.267 回答