JavaScript:如何创建对象并过滤这些属性? [复制]
Posted
技术标签:
【中文标题】JavaScript:如何创建对象并过滤这些属性? [复制]【英文标题】:JavaScript: How to create an Object and filter on those attributes? [duplicate] 【发布时间】:2010-12-14 06:17:45 【问题描述】:我有一个这样的 javascript 房屋数组,
"homes" : [
"home_id" : "1",
"address" : "321 Main St",
"city" : "Dallas",
"state" : "TX",
"zip" : "75201",
"price" : "925",
"sqft" : "1100",
"year_built" : "2008",
"account_type_id" : "2",
"num_of_beds" : "2",
"num_of_baths" : "2.0",
"geolat" : "32.779625",
"geolng" : "-96.786064",
"photo_id" : "14",
"photo_url_dir" : "\/home_photos\/thumbnail\/2009\/06\/10\/"
],
..........
我想提供 3 种不同的搜索方式。
我怎样才能返回这个家庭区域数组的一个子集:
X 和 Y 之间的price
bathrooms
>= Z
bedrooms
的编号 == A 或 == B 或 == C
例如,我将如何创建如下伪代码:
homes.filter price >= 150000, price <= 400000, bathrooms >= 2.5, bedrooms == 1 | bedrooms == 3
【问题讨论】:
请注意,您的数据结构中有一些应该是数字的字符串,并且您的 home 数组中有num_of_baths
和 num_of_beds
,但您在伪代码中使用了 bathrooms
和 bedrooms
.在我自己的示例中,这让我困惑了一段时间,因为这些条目不存在。 :-)
这里的另一个问题是您需要处理这些语句的不同连接方式。例如,价格语句由 &&(价格范围)连接,卧室语句将由 || 连接。将它们放在类似于伪代码中的列表中并不能说明差异。我添加了另一个解决此问题的答案。
这里有一些安静的答案,在某些时候你应该接受一个对你有用的答案。
【参考方案1】:
看看jQuery 库中的$.grep 函数。即使您不使用该库,您也可以查看代码并了解它们是如何完成您尝试执行的任务的。
编辑:这是一些代码:
function filter(houses, filters) // pass it filters object like the one in @J-P's answer
retArr = $.grep(houses,
function(house)
for(var filter in filters)
function test = filters[filter];
if(!test(house)) return false;
return true;
);
return retArr;
【讨论】:
+1 jQuery 就是为此而设计的——这就是标题中的“查询”一词。 $.grep 看起来可以在简单的数组上正常工作,但是我如何将它用于上面的示例数组类型? 这里让我想起了这句话“如果你只有一把锤子,你往往会把每一个问题都视为钉子。” - jQuery 和 $.grep 似乎与这个问题无关。如果我错了,请随时纠正我。这个问题似乎更多是关于迭代数据结构。 我提到 $.grep 是因为提问者希望能够根据一些条件语句过滤数组并获取原始数组的子集,而使用 jQuery 会增加您编写所需的代码量小一点。使用 $.grep 您只需将条件语句写入回调函数并让它返回它们的组合结果。我将修改我的答案以包含一些代码,也许这会使我的推理更加清晰。 我很想看到这个工作,我看到的以这种方式使用 grep 的示例通常是字符串或整数,而不是具有多个字段的对象。有趣的方法。【参考方案2】:我假设您希望能够在 home 数组上调用过滤器方向的语法糖来生成一个仅包含过滤器接受结果的新数组。
使用对象原型在接受参数过滤器散列的数组对象上定义过滤器函数。然后,您可以轻松地使用该哈希生成并返回与所需属性匹配的新房屋数组。
【讨论】:
我真的不在乎语法糖是什么样子,只要它生成子集数组。 虽然这被否决了,但它实际上是这里最正确的答案。过滤器(在 ECMA 第 5 版中引入)允许您根据 OP 的需要过滤数组。使用 polyfill(增强原型)来添加过滤器功能。【参考方案3】:看看 JSONPath http://code.google.com/p/jsonpath/
类似 jsonPath(json, "$..homes[?(@.price
JAQL 对于过滤 JSON 看起来也很有趣。 http://www.jaql.org/
【讨论】:
JSONPath 看起来很有趣。感谢您提及!【参考方案4】:给你:
var filteredArray = filter(myBigObject.homes,
price: function(value)
value = parseFloat(value);
return value >= 150000 && value <= 400000;
,
num_of_baths: function(value)
value = parseFloat(value);
return value >= 2.5;
,
num_of_beds: function(value)
value = parseFloat(value);
return value === 1 || value === 3;
);
还有filter
函数:
function filter( array, filters )
var ret = [],
i = 0, l = array.length,
filter;
all: for ( ; i < l; ++i )
for ( filter in filters )
if ( !filters[filter](array[i][filter]) )
continue all;
ret[ret.length] = array[i];
return ret;
【讨论】:
很好的解决方案,虽然我认为你忘了摆脱matches
@meder,是的,你是对的。已编辑:)
我不明白这是如何工作的,因为您已经硬编码了我要过滤的值。我希望能够做类似于 --> "homes.filter price >= A, price = C,卧室 == D | 卧室 == E"
你介意告诉我如何调用这个过滤函数吗?这可能会消除我的一些困惑
@GeorgeG,完全按照我的方式使用它。 filter
的第二个参数是一个对象,其属性名称对应于您要测试的对象,以及执行测试的函数。测试通过时函数返回 true,否则返回 false。【参考方案5】:
Array.prototype.select = function(filter)
if (!filter) return this;
var result = [], item = null;
for (var i = 0; i < this.length; i++)
item = this[i];
if (filter(item))
result.push(item);
return result;
function filterHomes(homes)
var a = 1, b = 2, c = 3, x = 4, y = 5, z = 6;
return homes.select(function(item)
return between(item.price, x, y) && item.num_of_baths >= z && inArray(item.num_of_beds, [a, b, c]);
);
function between(value, min, max)
return value >= min && value <= max;
function inArray(value, values)
for (var i = 0; i < values.length; i++)
if (value === values[i]) return true;
return false;
【讨论】:
这是一个有趣的解决方案,将 Array.prototype 扩展为包含过滤器功能。不错。 是的,这正是我们的建议。 这有什么好处,因为您在我的过滤器中硬编码以使用全局 JS 变量 我不太确定你的意思。在这种情况下,变量成为闭包的一部分。这样做的目的是扩展 Array 对象以允许您根据需要过滤其内容。 @ChaoesPandion,你介意告诉我如何调用你的代码吗?这可能会消除我的困惑【参考方案6】:我喜欢这个问题,所以我想试一试。像一些 JavaScript DOM 框架一样,“链式”代码风格如何,您拥有的对象会返回自身。
我正在调用你的对象MyObj
:
MyObj.filter('home.price >= 150000')
.filter('home.price <= 400000')
.filter('home.num_of_baths >= 2.5')
.filter('home.num_of_beds == 1 || home.bedrooms == 3');
这里是源代码,这个例子有效。
var MyObj =
filter : function(rule_expression)
var tmpHomes = [];
var home = ;
for(var i=0;i<this.homes.length;i++)
home = this.homes[i];
if (eval(rule_expression))
tmpHomes.push(home);
this.homes = tmpHomes;
return this;
,
homes: [
"home_id" : 1,
"address" : "321 Main St",
"city" : "Dallas",
"state" : "TX",
"zip" : "75201",
"price" : 300000,
"sqft" : 1100,
"year_built" : 2008,
"account_type_id" : 2,
"num_of_beds" : 1,
"num_of_baths" : 2.5,
"geolat" : 32.779625,
"geolng" : -96.786064,
"photo_id" : "14",
"photo_url_dir" : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
,
"home_id" : 2,
"address" : "555 Hello World Way",
"city" : "Dallas",
"state" : "TX",
"zip" : "75201",
"price" : 200000,
"sqft" : 900,
"year_built" : 1999,
"account_type_id" : 2,
"num_of_beds" : 1,
"num_of_baths" : 1.0,
"geolat" : 32.779625,
"geolng" : -96.786064,
"photo_id" : "14",
"photo_url_dir" : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
,
"home_id" : 3,
"address" : "989 Foo St",
"city" : "Dallas",
"state" : "TX",
"zip" : "75201",
"price" : 80000,
"sqft" : 1100,
"year_built" : 2003,
"account_type_id" : 2,
"num_of_beds" : 3,
"num_of_baths" : 3,
"geolat" : 32.779625,
"geolng" : -96.786064,
"photo_id" : "14",
"photo_url_dir" : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
,
"home_id" : 4,
"address" : "1560 Baz Rd",
"city" : "Dallas",
"state" : "TX",
"zip" : "75201",
"price" : 100000,
"sqft" : 1100,
"year_built" : 2008,
"account_type_id" : 2,
"num_of_beds" : 3,
"num_of_baths" : 1.5,
"geolat" : 32.779625,
"geolng" : -96.786064,
"photo_id" : "14",
"photo_url_dir" : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
]
;
【讨论】:
另一个很好的解决方案,它看起来确实非常接近问题中的伪代码。 @Ryan Lynch,是的,我很高兴我的现成代码几乎可以工作。然后我做了测试并改进了它。不过,JSONPath 看起来也非常有用。祝你项目好运! 我喜欢这个答案,虽然我对 eval 有点怀疑。 我同意对 eval 持怀疑态度,但我认为在这种情况下,由于您尝试对某些 JSON 执行类似 SQL 的查询,因此它创建了一个相当优雅的解决方案。我将此响应与在另一个答案中扩展 Array Prototype 相结合,请查看。【参考方案7】:Javascript 1.6(FF,基于Webkit)内置了Array.filter 函数,因此无需重新发明***。
result = homes.
filter(function(p) return p.price >= 150000 ).
filter(function(p) return p.price <= 400000 ).
filter(function(p) return p.bathrooms >= 2.5 ) etc
有关 msie 回退,请参阅 the page linked above。
【讨论】:
什么版本的 Firefox 和网站 (Chrome/Safari)。我阅读了上面的链接,但没有提到我在概述中看到的 IE 我认为更强大的方法是将 Array.prototype.filter 函数包装在一个单独的函数中,这样您就可以在被测试元素的范围内评估测试语句在 with 块中应用或评估。这样,您可以以更通用的方式编写测试函数,以便它们可以用于任何对象或对象中包含的任何键。查看我的第二个解决方案,了解我的意思。 感谢,@stereofrog。其实我希望我早点看到你的帖子。当我尝试通过编写同名函数来扩展 Array.prototype 时,我偶然“发现”了 Array.prototype.filter 方法。你本可以为我节省 5 分钟的调试时间和一个面。 @RyanLynch: "我认为这样做更强大的方法是将 Array.prototype.filter 函数包装在一个单独的函数中,以便您可以在范围内评估测试语句在 with 块中使用 apply 或 eval 测试的元素的数量。” 如果我理解正确,您正在寻找filter
的第二个参数,这将用作 this
时调用回调。【参考方案8】:
Underscore.js 库非常适合这些任务。当它们存在时,它使用本机 JavaScript 命令。我已经设法使用这个库获得了许多迭代循环。
奖励,最新版本是可链接的,就像 jQuery。
【讨论】:
【参考方案9】:o = (
'homes' :
[
"home_id" : "1",
"address" : "321 Main St",
"city" : "Dallas",
"state" : "TX",
"zip" : "75201",
"price" : "20000",
"sqft" : "1100",
"year_built" : "2008",
"account_type_id" : "2",
"num_of_beds" : "3",
"num_of_baths" : "3.0",
"geolat" : "32.779625",
"geolng" : "-96.786064",
"photo_id" : "14",
"photo_url_dir" : "\/home_photos\/thumbnail\/2009\/06\/10\/"
])
Array.prototype.filterBy = function( by )
outer: for (
var i = this.length,
ret = ,
obj;
i--;
)
obj = this[i];
for ( var prop in obj )
if ( !(prop in by) ) continue
if ( by[prop](obj[prop]) )
ret[prop] = obj[prop]
return ret;
var result = o.homes.filterBy(
price:function(price)
price = parseFloat(price)
return price >= 15000 && price <=40000
,
num_of_baths:function(bathroom)
bathroom = parseFloat(bathroom)
return bathroom > 2.5
,
num_of_beds:function(bedroom)
bedroom = parseFloat(bedroom)
return bedroom === 1 || bedroom === 3
);
for ( var p in result ) alert(p + '=' + result[p])
【讨论】:
【参考方案10】:我已经多次修改了这个答案,如果对修改感兴趣,人们可以查看 wiki,但这是我想出的最终解决方案。它与此处发布的许多其他内容非常相似,主要区别在于我扩展了 Array.prototype
包装了 Array.prototype.filter
函数,以便在数组元素而不是数组的范围内评估测试语句。
我看到这个解决方案的主要优点是它允许您编写测试this[key]
值的通用测试,而不是简单地直接使用filter
方法。然后,您可以创建与特定测试关联的通用表单元素,并使用要过滤的对象(在本例中为房屋)中的元数据将表单元素的特定实例与键关联。这不仅使代码更具可重用性,而且我认为它实际上使以编程方式构建查询更加直接。此外,由于您可以定义通用表单元素(例如,不同值的多选,或范围的一组下拉菜单),您还可以创建更强大的查询界面,其中可以动态注入额外的表单元素,允许用户创建复杂和定制的查询。下面是代码分解成的内容:
在做任何事情之前,您应该首先检查filter
函数是否存在,如果不存在,则扩展Array.prototype
,如其他解决方案中所述。然后扩展Array.prototype
以包装filter
函数。我已经这样做了,以便参数是可选的,以防有人想要使用由于某种原因不需要任何测试函数,我还尝试包含错误以帮助您实现代码:
Array.prototype.filterBy = function(testFunc, args)
if(args == null || typeof args != 'object') args = [args];
if(!testFunc || typeof testFunc != 'function') throw new TypeError('argument 0 must be defined and a function');
return this.filter(function(elem)
return testFunc.apply(elem, args);
);
;
这需要一个测试函数和一个参数数组,并定义一个内联函数作为Array.prototype.filter
函数的回调,该函数调用function.prototype.apply
以在使用指定的被测试的数组元素的范围内执行测试函数论据。然后,您可以编写一套通用测试函数,如下所示:
testSuite =
range : function(key, min, max)
var min = parseFloat(min);
var max = parseFloat(max);
var keyVal = parseFloat(this[key]);
if(!min || !max|| !keyVal) return false;
else return keyVal >= min && keyVal <= max;
,
distinct : function(key, values)
if(typeof key != 'string') throw new TypeError('key must be a string');
if(typeof values != 'object') values = [values];
var keyVal = this[key];
if(keyVal == undefined) return false;
for(var i in values)
var value = values[i];
if(typeof value == 'function') continue;
if(typeof value == 'string')
if(keyVal.toString().toLowerCase() == value.toLowerCase()) return true;
else continue;
else
keyVal = parseFloat(keyVal);
value = parseFloat(value);
if(keyVal&&value&&(keyVal==value)) return true;
else continue;
return false;
;
这些只是与您的问题中的要求相关联的测试函数,但是您可以创建额外的函数来执行更复杂的事情,例如针对正则表达式测试字符串值,或测试不是简单数据类型的键值。
然后您像这样扩展包含房屋数组的对象(我将其称为房屋数据,无论您的代码中如何称呼它):
housesData.filterBy = function(tests)
ret = this.homes.slice(0);
if(tests)
for(var i in tests)
var test = tests[i];
if(typeof test != 'object') continue;
if(!test.func || typeof test.func != 'function') throw new TypeError('argument 0 must be an array or object containing test objects, each with a key "func" of type function');
else ret = ret.filterBy(test.func, test.args ? test.args : []);
return ret;
然后您可以像这样调用此函数,以使用上面定义的通用函数获取结果集:
result = housesData.filterBy([func:range,args:['price','150000','400000'],
func:distinct,args:['num_of_bedsf',[1, 2, 3]]]);
然而,我实际上预计使用它的方式是将我之前提到的通用表单元素序列化为测试对象的数组或哈希图。这是我用来测试这段代码的一个简单示例(我使用了 jQuery,因为它更简单,之前的所有代码都在 example.js 中,以及@artlung 的 dummy home 数组):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:myNS="http://uri.for.your.schema" xml:lang="en" lang="en">
<head>
<script src="jquery-1.3.2.js" type="text/javascript" language="javascript"></script>
<script src="example.js" type="text/javascript" language="javascript"></script>
<script type="text/javascript" language="javascript">
runQuery = function(event)
var tests = [];
$('#results').attr('value', '');;
controls = $('#theForm > fieldset');
for(var i = 0; i < controls.length ; i++)
var func;
var args = [];
control = controls.eq(i);
func = testSuite[control.attr('myNS:type')];
args.push(control.attr('myNS:key'));
var inputs = $('input', control);
for(var j=0; j< inputs.length; j++)
args.push(inputs[j].value);
tests.push(func:func,args:args);
result = housesData.filterBy(tests);
resultStr = '';
for(var i = 0; i < result.length; i++)
resultStr += result[i]['home_id'];
if(i < (result.length -1)) resultStr += ', ';
$('#results').attr('value', resultStr);
</script>
</head>
<body>
<form id="theForm" action="javascript:null(0)">
<fieldset myNS:type="range" myNS:key="price">
<legend>Price:</legend>
min: <input type="text" myNS:type="min"></input>
max: <input type="text" myNS:type="max"></input>
</fieldset>
<fieldset myNS:type="distinct" myNS:key="num_of_beds">
<legend>Bedrooms</legend>
bedrooms: <input type="text" myNS:type="value"></input>
</fieldset>
<button onclick="runQuery(event);">Submit</button>
</form>
<textarea id="results"></textarea>
</body>
</html>
【讨论】:
以这种方式处理查询的真正酷之处在于,您可以从 JSON 中读取键并按需为它们动态注入表单元素,从而允许人们创建复杂的查询。例如,用户可以选择一个邮政编码范围以及该范围之外的不同邮政编码值。您还可以为房屋对象添加额外的键,而不必更新网站的代码。 Ryan,非常感谢您的详细回答。快速提问,所以我需要的唯一代码如下? Houses.filterBy = function(tests) try var tmpHouses = this.houses.slice(0); for(测试中的测试) tmpHouses = tmpHouses.filterBy(test.func, test.args); catch(e) // 错误处理 return []; 返回 tmpHouses; 哈,我不会把它称为彻底修改的那么详细。 ;-) 如果您打算使用 function.prototype.apply 路线(正如人们指出的那样,这可能比使用 eval 更好),您需要使用上面代码块中的 filterBy 函数扩展 Array 原型你引用,以及编写要传递给它的测试函数。您可以使用我之前编写的 distinct 和 range 函数,只需让它们使用 this[key] 评估 statemet 并返回结果而不是作为字符串的语句。 George,这个答案有点冗长而杂乱无章,所以我将对其进行修改,使其更加专注于您正在寻找的解决方案。希望这会更有帮助。【参考方案11】:我同意不重新发明***,只使用原生数组过滤器方法。
如果缺少filter
,Mozilla 页面上显示的函数定义将通过使用对象检测(if (!Array.prototype.filter)
)添加,无论使用何种浏览器。
任何建议eval
和/或多个for
循环的方法都是slow and potentially unsafe。
您没有这么说,但我假设这些值将来自用户输入(表单),所以我会这样做:
var filtered_results = obj.homes
.filter(function (home)
return parseInt(home.price) >= document.getElementsByName('price_gt')[0].value;
)
.filter(function (home)
return parseInt(home.price) <= document.getElementsByName('price_lt')[0].value;
)
.filter(function (home)
return parseFloat(home.num_of_baths) >= document.getElementsByName('baths_gt')[0].value;
);
更好的是,不要遍历列表 N 次,只需这样做:
var filtered_results = obj.homes.filter(function (home)
return (
parseInt(home.price) >= document.getElementsByName('price_gt')[0].value &&
parseInt(home.price) <= document.getElementsByName('price_lt')[0].value &&
parseFloat(home.num_of_baths) >= document.getElementsByName('baths_gt')[0].value
);
);
我知道这与请求的语法不匹配,但这种方法更快、更灵活(不使用 eval 或硬编码值)。
【讨论】:
它可能会更快,但我不确定它是否更灵活。您可能不会对值进行硬编码,但您在对逻辑进行硬编码。如果您构造字符串来表示条件语句,您可以轻松地扩展查询以获取主对象中的其他键。您甚至可以从要查询的对象中读取键,然后创建从键字符串和一组值(键 > min && key 我将通用函数添加到我的(第二个)答案中,以解释我对灵活性的意思。【参考方案12】:Ryan Lynch - 如果您使用字符串进行比较(“值运算符比较”),您可以使用纯 JavaScript(值运算符比较)。逻辑上的不灵活性是相同的,只是速度和安全性较低(通过 eval())。
我的示例中缺乏灵活性来自于知道哪些字段(价格、浴室等)以及我们对哪些运算符感兴趣,但原始发帖人的请求列出了具体的字段和比较(价格 Y,等)。
GeorgeG - 如果您有兴趣,并且我对来自表单的过滤器值的假设是正确的,我可以编写一个更通用的“对于每个用户指定的值,根据请求过滤结果”方法但是我会等待确认,如果可能的话,我会等待代码示例。
【讨论】:
当我说您的解决方案不太灵活时,我之所以这么说是因为您将对象及其属性硬编码到您的测试函数中(即 home.price)。我添加了一个类似的解决方案,它使用 apply 而不是 eval 在元素上下文中执行语句。我的目标是使代码尽可能少地依赖于被测试对象的特定属性,以及值的来源(即表单元素)。这使得代码可以在提问者之外重用。 我还应该指出,我并不是要侮辱您的解决方案,因为实际上我的解决方案与您的解决方案和此处的其他几个解决方案相似。我只是认为运行测试的最可重用的方法是在被测试元素的上下文中执行它们,方法是包装过滤器函数,以便您可以使用 eval 或 apply 在范围内运行测试数组元素。【参考方案13】:有趣的是,这些答案中的大多数除了尝试更优雅地对数组进行迭代之外什么都不做,而这甚至不是 GeorgeG 所需要的。
假设你有一个巨大的 JSON,你想要一些快速的东西。迭代不是这样的。您需要 jOrder (http://github.com/danstocker/jorder),它在 1000 行表上的搜索速度比迭代过滤快 100 倍。表格越大,比率越高。
这就是你要做的。
由于 jOrder 一次不能处理多个不等式过滤器,因此您必须分三步完成,但速度仍然会快很多。
首先,根据您的原始数据创建一个 jOrder 表,并在其上添加价格索引:
var table = jOrder(data.homes)
.index('price', ['price'], ordered: true, grouped: true, type: jOrder.number );
您可以在以后的搜索中重复使用此表。现在您首先获取价格过滤结果,并将其包装在另一个 jOrder 表中,最后添加一个浴室索引:
var price_filtered = jOrder(table.where([ price: lower: 150000, upper: 400000 ]))
.index('bathrooms', ['num_of_baths'], ordered: true, grouped: true, type: jOrder.number );
然后我们继续对浴室做同样的事情,在卧室上添加一个索引,因为我们不会在这里使用不等式过滤器,所以不必对其进行排序:
var bath_filtered = jOrder(price_filtered.where([ num_of_baths: lower: 2.5 ]))
.index('bedrooms', ['num_of_beds'], grouped: true );
最后,你得到完全过滤的集合:
var filtered = jOrder(bath_filtered.where([ num_of_beds: 1 , num_of_beds: 3 ]));
当然,您可以将所有这些包装到一个接受三个参数的函数中:
price: lower: 150000, upper: 400000
num_of_baths: lower: 2.5
[ num_of_beds: 1 , num_of_beds: 3 ]
并返回filtered
。
【讨论】:
以上是关于JavaScript:如何创建对象并过滤这些属性? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
在 javascript 中过滤一个对象对象(过滤或减少?)
根据 GPS 位置之间的距离过滤对象数组(javascript)