一行代码,干掉我 10 行!聊聊 .filter(Boolean)
这个让我又爱又恨的小恶魔 😎
今天不聊什么高大上的架构,也不谈什么底层的源码。咱们来聊一个特别小、特别不起眼,但一旦你用熟了,就再也回不去的小技巧 ------ .filter(Boolean)
。
相信我,这篇文章会带你回到几个我亲身经历的项目"事故"现场,看看我是如何从抓耳挠腮,到用它"一键清屏",再到被它狠狠"背刺"的。准备好小板凳,故事开始了!😉
场景一:那个逼死强迫症的"幽灵标签" 👻
那是一个风和日丽的下午,我正在开发一个文章发布系统,里面有个标签(Tags)功能。需求很简单:用户在输入框里用逗号分隔输入标签,比如 "JavaScript, React, Vue"
,然后前端解析成一个个漂亮的标签展示出来。
很简单嘛,split
一下不就好了?我三下五除二就写完了:
javascript
const tagsInput = "JavaScript, React, Vue";
const tagsArray = tagsInput.split(',').map(tag => tag.trim());
// -> ["JavaScript", "React", "Vue"]
// 看起来完美!🚀
但"真实"的用户永远会给你"惊喜"。很快,测试同学就提了一个 Bug。
原来,用户可能会这么输入:"JavaScript, , React, ,Vue,"
。连续的逗号、或者结尾的逗号,经过我的代码处理后,会产生什么呢?
javascript
const tagsInput = "JavaScript, , React, ,Vue,";
const tagsArray = tagsInput.split(',').map(tag => tag.trim());
// -> ["JavaScript", "", "React", "", "Vue", ""]
看到了吗?那一堆空字符串 ""
就这样混了进去,渲染到页面上,就成了一个个没有文字的"幽灵标签"。丑到爆炸!💥
我最初的笨办法
我当时的第一反应就是,再加个 filter
把空字符串过滤掉呗。
javascript
// Before
const tagsArray = tagsInput.split(',').map(tag => tag.trim());
// After (My clumsy fix)
const cleanTags = tagsArray.filter(tag => tag !== "");
// -> ["JavaScript", "React", "Vue"]
// 问题解决了,但总觉得不够优雅...🤔
这代码能跑,但它只处理了空字符串。如果哪天数据源变了,混进来了 null
或者 undefined
呢?我是不是得写成 tag !== "" && tag !== null && tag !== undefined
?这也太啰嗦了!
"恍然大悟"的瞬间 💡
就在这时,我旁边座位的资深同事,人狠话不多的"K哥",瞥了一眼我的屏幕,敲下了一行代码:
javascript
const cleanTags = tagsArray.filter(Boolean);
我当时整个人都愣住了。filter(Boolean)
?这是什么魔法?🤯
K哥解释说,filter
方法需要一个返回"真值 (Truthy)"或"假值 (Falsy)"的函数。而 Boolean
本身就是一个函数,可以把任何值强制转换为 true
或 false
。在 JavaScript 中,有这么几个"假值":
false
0
""
(空字符串)null
undefined
NaN
除了它们,其他所有值都是"真值",比如 []
、{}
、非空字符串等。
所以,tagsArray.filter(Boolean)
就相当于把数组里的每一个元素都扔进 Boolean()
函数里过一遍,是"假值"的统统滚蛋,是"真值"的才配留下!
我的那个幽灵标签问题,无论是 ""
、null
还是 undefined
,都能被这一行代码优雅地解决掉。我的天,这简直太酷了!
场景二:动态构建 CSS 类名的"优雅体操" 🤸
尝到甜头后,我开始在更多地方"滥用"它。比如,在 Vue 或 React 里动态构建组件的 class。
我需要写一个通用的<Button>
组件,它有不同的状态,比如 primary
, disabled
, loading
。
曾经的 if/else
大杂烩
一开始,我的代码可能是这样的:
javascript
function getButtonClasses(isPrimary, isDisabled, isLoading) {
const classes = ['btn'];
if (isPrimary) {
classes.push('btn--primary');
}
if (isDisabled) {
classes.push('btn--disabled');
}
if (isLoading) {
classes.push('btn--loading');
}
return classes.join(' ');
}
能用,但 if
语句太多了,不够"声明式",也不够酷。
.filter(Boolean)
的高光时刻
学到了新姿势,我立刻对它进行了改造。利用 &&
的短路特性,代码可以变成这样:
javascript
function getButtonClasses(isPrimary, isDisabled, isLoading) {
const classList = [
'btn',
isPrimary && 'btn--primary', // 如果 isPrimary 为 true,结果是 'btn--primary' (真值)
isDisabled && 'btn--disabled', // 如果 isDisabled 为 false,结果是 false (假值)
isLoading && 'btn--loading'
];
// classList 可能是 -> ['btn', 'btn--primary', false, 'btn--loading']
return classList.filter(Boolean).join(' ');
}
// 假设 isPrimary=true, isDisabled=false, isLoading=true
// 最终结果 -> "btn btn--primary btn--loading"
看到没?没有一个 if
语句,逻辑清晰,代码行数也少了。通过 &&
生成一个可能包含"假值"的数组,然后用 .filter(Boolean)
一键清洗,最后 join
在一起。整套动作行云流水,赏心悦目!
场景三:那个让我半夜修 Bug 的"惊天大坑" 😱
然而,技术就像一把双刃剑,在你用得最爽的时候,往往会给你最痛的一击。
当时我正在做一个电商后台的报表,需要展示一个商品库存列表。API 返回的数据大概是这样的:
javascript
const inventory = [
{ name: '苹果', stock: 150 },
{ name: '香蕉', stock: 0 }, // 注意这里!库存为 0
{ name: '橙子', stock: null }, // 数据异常,为 null
{ name: '葡萄', stock: 80 }
]
我需要提取所有的库存数量,并过滤掉那些无效的数据(比如 null
)。我当时想都没想,自信地敲下了我的"祖传秘方":
javascript
const stockLevels = inventory.map(item => item.stock);
// -> [150, 0, null, 80]
const validStockLevels = stockLevels.filter(Boolean);
console.log(validStockLevels);
// 我以为会是:[150, 0, 80]
// 实际输出是:[150, 80]
代码上线了。第二天早上,客户就打来了电话:"为什么我们那个卖完了的'香蕉',在库存报表里直接消失了?!我们还想根据这个列表补货呢!"
我当时就懵了... 😅
这可把我坑惨了!我光记着 .filter(Boolean)
能干掉 null
和 undefined
,却忘了它一视同仁,把数字 0
也当成"假值"给干掉了!在这个场景里,0
是一个有意义的、合法的业务数据,代表"已售罄",它不应该被过滤掉。
填坑与反思
这个 Bug 让我半夜爬起来紧急修复,也让我对这个小技巧有了更深刻的认识。
正确的修复方法 应该是明确地告诉 filter
你到底想过滤掉什么:
javascript
// The SAFE way when 0 is a valid value
const validStockLevels = stockLevels.filter(stock => stock !== null && stock !== undefined);
// -> [150, 0, 80] 这才是我想要的结果!
前辈的总结时间 👨🏫
好了,故事讲完了。现在让我们像个老手一样,总结一下 .filter(Boolean)
的正确使用姿势:
-
它是什么? 一个超级简洁的工具,用于快速地、一次性地从数组中移除所有"假值" (
false
,0
,""
,null
,undefined
,NaN
)。 -
什么时候用它? (The Good Parts 👍)
- 清理脏数据 :当你在处理用户输入、API 返回等不确定的数据源,想快速去掉所有
null
,undefined
,""
时,它是你的最佳拍档。 - 条件性构建 :在动态构建数组(比如 CSS 类名、URL 参数)时,配合
&&
短路求值,能写出非常优雅的代码。
- 清理脏数据 :当你在处理用户输入、API 返回等不确定的数据源,想快速去掉所有
-
什么时候【千万别】用它? (The Bad Parts 👎)
- 当
0
是一个有意义的、需要被保留的有效数据时! 这是最大的坑,切记!比如库存数量、用户ID、数组索引等。 - 在这种情况下,请老老实实地使用更明确的条件判断,比如
item => item !== null
。
- 当
技术没有好坏之分,只有是否用对地方。.filter(Boolean)
就是这样一个典型的例子,它像一个聪明又有点小脾气的"小恶魔",你得摸透它的脾性,才能让它安全地为你所用。
希望这篇文章能帮你少走一些我走过的弯路!下次见!👋