9 个 JavaScript 单行代码可替代 50 行代码
我们所有人,有时,看到一堵可怕的 JavaScript 代码,心里暗自咒骂,心里很清楚应该有更好的方法。
经过一段时间的学习,我发现了一些简洁的单行代码,它们可以省去很多冗长的代码。
这些是真正有用且易读的技巧,利用现代 JavaScript 功能来解决常见问题。
因此,无论您是清理代码还是刚开始一个新项目,这些技巧都可以帮助您编写更优雅、更易于维护的代码。
这里有 9 个您今天就可以使用的巧妙的俏皮话。
扁平化嵌套数组
有没有试过展平如此深的数组?过去,这意味着很多复杂的多重循环、临时数组,以及太多的代码。
但现在它在一个强大的单行代码中非常漂亮地执行:
const flattenArray = arr => arr.flat(Infinity); const nestedArray = [1, [2, 3, [4, 5, [6, 7]]], 8, [9, 10]]; const cleanArray = flattenArray(nestedArray); // Result: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
如果你用更传统的方式来做这件事,你将会得到如下的结果:
function flattenTheHardWay(arr) { let result = []; for (let i = 0; i < arr.length; i++) { if (Array.isArray(arr[i])) { result = result.concat(flattenTheHardWay(arr[i])); } else { result.push(arr[i]); } } return result; }
所有繁琐的工作都由 flat() 处理,添加 Infinity 会告诉它下降到任何可能的水平。简单、干净,而且有效。
对象转换:无依赖关系的深度克隆
如果你需要一个真正的对象深度克隆,而不需要引入 lodash?这里有一个零依赖解决方案,可以处理嵌套对象、数组甚至日期:
const deepClone = obj => JSON.parse(JSON.stringify(obj)); const complexObj = { user: { name: 'Alex', date: new Date() }, scores: [1, 2, [3, 4]], active: true }; const cloned = deepClone(complexObj);
老办法?你必须输入如下内容:
function manualDeepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; if (obj instanceof Date) return new Date(obj); const clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] = manualDeepClone(obj[key]); } } return clone; }
**快速提示:**此单行代码确实有一些限制 - 它无法处理函数、符号或循环引用。但对于 90% 的使用情况,它几乎是正确的。
字符串处理:将 CSV 转换为对象数组
这是一个很棒的小单行代码,它接受 CSV 数据并输出可操作的对象数组,非常适合用于 API 响应或读取数据:
const csvToObjects = csv => csv.split('\n').map(row => Object.fromEntries(row.split(',').map((value, i, arr) => [arr[0].split(',')[i], value]))); const csvData = `name,age,city Peboy,30,New York Peace,25,San Francisco Lara,35,Chicago`; const parsed = csvToObjects(csvData); // Result: // [ // { name: 'Peboy', age: '30', city: 'New York' }, // { name: 'Peace', age: '25', city: 'San Francisco' }, // { name: 'Lara', age: '35', city: 'Chicago' } // ]
过时了吗?哦,你可能会写类似这样的东西:
function convertCSVTheHardWay(csv) { const lines = csv.split('\n'); const headers = lines[0].split(','); const result = []; for (let i = 1; i < lines.length; i++) { const obj = {}; const currentLine = lines[i].split(','); for (let j = 0; j < headers.length; j++) { obj[headers[j]] = currentLine[j]; } result.push(obj); } return result; }
这是一种使用单行命令进行数据转换的有效方法,但在投入生产之前需要添加一些错误处理。
数组操作:删除重复项和排序
这是一个缩短的单行代码,可以同时删除重复项并对您的数组进行排序,非常适合清理数据集:
const uniqueSorted = arr => [...new Set(arr)].sort((a, b) => a - b); // Example of its use: const messyArray = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]; const cleaned = uniqueSorted(messyArray); // Result: [1, 2, 3, 4, 5, 6, 9] // For string sorting const messyStrings = ['banana', 'apple', 'apple', 'cherry', 'banana']; const cleanedStrings = [...new Set(messyStrings)].sort(); // Result: ['apple', 'banana', 'cherry']
以前的方式是这样的:
function cleanArrayManually(arr) { const unique = []; for (let i = 0; i < arr.length; i++) { if (unique.indexOf(arr[i]) === -1) { unique.push(arr[i]); } } return unique.sort((a, b) => a - b); }
Set 完美地处理了重复项,然后扩展运算符将其重新转换为数组。之后您只需调用 sort() 即可!
DOM 操作:查询和转换多个元素
这是一个功能强大的单行代码,可以让你一次查询和转换多个 DOM 元素:
const modifyElements = selector => Array.from(document.querySelectorAll(selector)).forEach(el => el.style); // Use it like this: const updateButtons = modifyElements('.btn') .map(style => Object.assign(style, { backgroundColor: '#007bff', color: 'white', padding: '10px 20px' })); // Or even simpler for class updates: const toggleAll = selector => document.querySelectorAll(selector).forEach(el => el.classList.toggle('active'));
传统方法是:
function updateElementsManually(selector) { const elements = document.querySelectorAll(selector); for (let i = 0; i < elements.length; i++) { const el = elements[i]; el.style.backgroundColor = '#007bff'; el.style.color = 'white'; el.style.padding = '10px 20px'; } }
这适用于所有现代浏览器,并可节省您编写重复的 DOM 操作代码。
具有干净错误处理的并行 API 调用
这是另一条干净的线路,一行程序可以并行调用 API,并且以非常干净的错误处理方式执行。
const parallelCalls = urls => Promise.allSettled(urls.map(url => fetch(url).then(r => r.json()))); // Put it to work: const urls = [ 'https://api.example.com/users', 'https://api.example.com/posts', 'https://api.example.com/comments' ]; // One line to fetch them all: const getData = async () => { const results = await parallelCalls(urls); const [users, posts, comments] = results.map(r => r.status === 'fulfilled' ? r.value : null); };
更详细的说法是:
async function fetchDataManually(urls) { const results = []; for (const url of urls) { try { const response = await fetch(url); const data = await response.json(); results.push({ status: 'fulfilled', value: data }); } catch (error) { results.push({ status: 'rejected', reason: error }); } } return results; }
`Promise.allSettled` 是这里的主角;如果一个请求失败它也不会失败,并且它会为每次调用返回干净的状态信息。
日期/时间格式:无需库即可清理日期字符串
这是一个很棒的单行代码,它可以将日期转换为干净、可读的字符串,而无需任何外部依赖:
const formatDate = date => new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'short' }).format(date); const now = new Date(); console.log(formatDate(now)); // Output: "Thursday, December 3, 2024 at 2:30 PM" // Want a different format? Easy: const shortDate = date => new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).format(date); // Output: "Dec 3, 2024"
传统的做法是这样的:
function formatDateManually(date) { const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const dayName = days[date.getDay()]; const monthName = months[date.getMonth()]; const day = date.getDate(); const year = date.getFullYear(); const hours = date.getHours(); const minutes = date.getMinutes(); const ampm = hours >= 12 ? 'PM' : 'AM'; return `${dayName}, ${monthName} ${day}, ${year} at ${hours % 12}:${minutes.toString().padStart(2, '0')} ${ampm}`; }
`Intl.DateTimeFormat` 处理所有繁重工作,包括本地化。无需再手动构建日期字符串!
事件处理:消除抖动,避免臃肿
这是一个简洁的单行代码,可以创建任何函数的去抖动版本 - 非常适合搜索输入或窗口调整大小处理程序:
const debounce=(fn,ms)=>{let timeout;return(...args)=>{clearTimeout(timeout);timeout=setTimeout(()=>fn(...args),ms);};}; // Put it to work: const expensiveSearch=query=>console.log('Searching for:',query); const debouncedSearch=debounce(expensiveSearch,300); // Use it in your event listener: searchInput.addEventListener('input',e=>debouncedSearch(e.target.value));
传统的方式如下:
function createDebounce(fn, delay) { let timeoutId; return function debounced(...args) { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { fn.apply(this, args); timeoutId = null; }, delay); }; }
这一行代码涵盖了所有基本的去抖动用例,并节省了您不必要地调用函数的时间,尤其是在输入或调整大小等快速连续生成输入时。
本地存储:带验证的对象存储
这里只是另一个简洁的单行代码,它使用内置验证和错误处理来处理 localStorage 中的对象存储:
const store = key => ({ get: () => JSON.parse(localStorage.getItem(key)), set: data => localStorage.setItem(key, JSON.stringify(data)), remove: () => localStorage.removeItem(key) }); // Use it like this: const userStore = store('user'); // Save data userStore.set({ id: 1, name: 'John', preferences: { theme: 'dark' } }); // Get data const userData = userStore.get(); // Remove data userStore.remove();
旧方法需要这样的东西:
function handleLocalStorage(key) { return { get: function() { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : null; } catch (e) { console.error('Error reading from localStorage', e); return null; } }, set: function(data) { try { localStorage.setItem(key, JSON.stringify(data)); } catch (e) { console.error('Error writing to localStorage', e); } }, remove: function() { try { localStorage.removeItem(key); } catch (e) { console.error('Error removing from localStorage', e); } } }; }
包装器为您提供了一个用于 localStorage 操作的干净 API,并自动处理所有 JSON 解析/字符串化。
总结
这些单行代码不仅能减少代码编写量,还能让代码编写更智能。每行代码都能以简洁、可维护的方式解决常见的 JavaScript 难题。虽然这些代码片段功能强大,但请记住,可读性始终是第一位的。如果一行代码让您的代码更难理解,请将其拆分成多行。
您可以随意在您的项目中混合搭配这些模式,如果您支持旧版浏览器,请不要忘记检查浏览器对较新的 JavaScript 功能(如“flat()”或“Intl.DateTimeFormat”)的兼容性。
你有自己强大的 JavaScript 单行代码吗?我很想看看!