我跟你说,有时候 JavaScript 的魔法就藏在那些你一眼扫过,却瞬间让你拍案叫绝的简短代码里。它们不像那些洋洋洒洒几百行的“大制作”,但每一行都精准地切中了要点,带来了出其不意的效果。我最近就挖到了一些这样的“小宝石”,想跟你分享一下,保证不是那种看了跟没看一样、让人昏昏欲睡的“干货”。
1. “一键”隐藏/显示:CSS 切换的艺术
你有没有遇到过那种,点击一下按钮,某个区域就优雅地出现或者消失的场景?很多人可能会想到用 JS 去直接操作元素的 `display` 属性,就像这样:
```javascript
document.getElementById('myButton').onclick = function() {
var element = document.getElementById('myContent');
if (element.style.display === 'none') {
element.style.display = 'block'; // 或者 'flex', 'grid' 等
} else {
element.style.display = 'none';
}
};
```
这没毛病,但如果我告诉你,用 CSS 配合一个简单的 JS 事件监听,就能让代码更简洁,并且更符合关注点分离的原则呢?
```javascript
// HTML:
//
切换 //
内容区域
// CSS:
// .hidden { display: none; }
// JavaScript:
document.getElementById('toggleBtn').addEventListener('click', () => {
document.getElementById('contentToToggle').classList.toggle('hidden');
});
```
看出来了吗?我们把显示/隐藏的逻辑放到了一个 `hidden` 类上。JS 的 `classList.toggle('hidden')` 方法实在是太聪明了!它就像一个开关,如果元素上有 `hidden` 类,它就帮你移除;如果没有,它就帮你加上。这样一来,你只需要关注点击事件,而真正的样式变化则交给 CSS 来处理。这是一种非常“优雅”的解决方案,让你的代码更具可读性,也更容易维护。想象一下,如果将来你想让内容不是简单地 `display: none`,而是平滑地淡入淡出,你只需要修改 CSS,JS 代码完全不用动!
2. 数组去重:一行代码的“净化术”
处理数据的时候,最让人头疼的就是重复项了。特别是在前端,我们经常需要处理用户输入或者从 API 获取的数据。如果有一堆 ID 或者名字混在一起,去重可是个体力活。
传统的做法可能会用一个新数组,遍历原数组,如果新数组里没有当前元素,就把它加进去。效率嘛,也就那样。但 JavaScript 的新特性,特别是 `Set` 对象,简直是为去重量身定做的。
```javascript
const numbers = [1, 2, 2, 3, 4, 4, 5, 1];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // 输出: [1, 2, 3, 4, 5]
```
我跟你说,这简直是“净化术”!`new Set(numbers)` 会创建一个只包含唯一值的 `Set` 集合。然后,我们用展开运算符 (`...`) 将这个 `Set` 转换回一个数组。一行代码,搞定!而且效率高得惊人,因为 `Set` 在内部查找元素时使用了哈希表,比你在数组里一遍遍 `indexOf` 要快得多。
3. “神奇的” `Array.prototype.reduce`:不只是求和
提到 `reduce`,很多人第一反应就是求数组的和。但 `reduce` 的强大之处远不止于此,它可以说是 JavaScript 数组操作中最具通用性的方法之一。它就像一个多才多艺的工具箱,你可以用它来构建各种复杂的数据转换。
举个例子,我们想统计一个数组中每个元素出现的次数:
```javascript
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const fruitCounts = fruits.reduce((accumulator, currentFruit) => {
// accumulator 是一个对象,用来存储计数
// 如果当前水果已经在 accumulator 中,就增加计数
// 否则,就给它一个初始计数 1
accumulator[currentFruit] = (accumulator[currentFruit] || 0) + 1;
return accumulator; // 返回更新后的 accumulator
}, {}); // 初始值是一个空对象 {}
console.log(fruitCounts); // 输出: { apple: 3, banana: 2, orange: 1 }
```
这里,`reduce` 的第一个参数是一个回调函数,它接收两个主要参数:`accumulator`(累加器,也就是我们最终想要的结果)和 `currentValue`(当前正在处理的数组元素)。第二个参数 `{}` 是 `reduce` 的初始值,在这里我们提供了一个空对象。
每次迭代,我们都检查 `accumulator` 中是否已经存在当前的水果。如果存在,我们就把它的计数加一;如果不存在,我们就把它初始化为 1。这个 `(accumulator[currentFruit] || 0)` 的用法也很巧妙,如果 `accumulator[currentFruit]` 是 `undefined` (即第一次遇到这个水果),`|| 0` 就会提供一个默认值 0,然后再加 1,完美!通过这种方式,`reduce` 不仅能求和,还能构建对象、合并数组,甚至是实现复杂的过滤和映射逻辑。它的威力,真的可以挖掘很久。
4. 函数柯里化(Currying):函数的“精细化生产线”
柯里化是函数式编程中的一个重要概念,简单来说,就是把接受多个参数的函数,转换成一系列只接受一个参数的函数。听起来有点绕?别急,看个例子就懂了。
假设我们有一个函数,需要三个参数来计算:
```javascript
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
```
用柯里化的思路,我们可以把它变成这样:
```javascript
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(curriedAdd(1)(2)(3)); // 6
```
看到了吧?`curriedAdd(1)` 返回一个函数,这个函数接收 `b`;再调用 `(2)`,又返回一个函数接收 `c`;最后调用 `(3)`,才得到最终结果。
这样做的好处是,我们可以“预设”一些参数。比如,你经常需要计算 `1 + x + y`:
```javascript
const addOne = curriedAdd(1);
console.log(addOne(2)(3)); // 6
console.log(addOne(5)(10)); // 16
```
`addOne` 这个函数已经被固定了第一个参数为 1,你就可以重复使用它,而不用每次都写 `curriedAdd(1, ...)`。
当然,手动实现柯里化函数会比较繁琐。社区里有很多工具函数(比如 Lodash 的 `_.curry`)可以帮你自动完成这个转换,一行代码就可以让你的函数变得“可定制化”。这种方式让函数更加灵活,便于组合和复用,是函数式编程思想的体现。
小结一下
这些只是冰山一角,JavaScript 的世界里还有太多这样的“短小精悍”的代码片段,它们或巧妙,或高效,或富有表现力。关键在于理解背后的原理和思想。每次当我发现一个新的技巧时,都感觉像是解锁了一个新的技能点,真的很有成就感。下次你在写 JS 的时候,不妨也多留心一下,或许下一个让你惊叹的“魔法”,就在你指尖的键盘上。