JavaScript功夫:掌握优雅编程技巧

115 阅读12分钟

JavaScript 不仅仅是一门编程语言,更是一门需要精湛技艺的手艺。就像任何一门武术一样,新手与大师的区别在于你所掌握的技巧。任何人都能写出能运行的代码,但写出干净、强大且优雅表达的代码才是真正的 JavaScript 功夫的开始。

让我们深入探讨这些高级 JavaScript 技巧,它们能让你的代码脱颖而出。无论你是在构建企业级软件还是仅仅磨练技能,这些模式都将帮助你写出更干净、更快且更易维护的代码——那种能让未来的你(和团队成员)肃然起敬的代码。

我们将涵盖以下内容:

  • 超越基础的高级 console.log 实践
  • 使用ES6特性如模板字符串、解构和展开运算符来编写更清晰直观的逻辑
  • 通过短路求值编写更具表达力的条件语句
  • 使用console.table()console.time()等工具进行调试和性能分析
  • 像专家一样合并数组,即使有重复元素

把这看作你掌握现代 JavaScript的道场。你已经掌握了基础,现在准备更上一层楼。

让我们像大师一样编写代码。

1. Console.log技巧:专业级的日志记录

在 JavaScript 开发的早期阶段,许多开发者严重依赖基本的console.log()语句。虽然它能完成任务,但高级开发者知道有更强大、更干净的日志记录方式,特别是在处理结构化对象、调试或分析性能时。考虑这个简单例子:

const student = { name: 'Ray', age: 20 };
const tutor = { name: 'Peter', age: 40 };

基础日志记录(难以阅读)

console.log(student);
console.log(tutor);

虽然这样可行,但在处理多个变量时很难区分日志。现在,让我们探索更干净的替代方案。

2. 计算属性日志记录

与其逐个记录变量,不如使用对象属性简写将日志分组到一个对象中,使其更易读:

console.log({ student, tutor });

// 输出:
{ student: { name: 'Ray', age: 20 }, tutor: { name: 'Peter', age: 40 } }

现在日志一目了然,清晰易读——非常适合调试或检查嵌套数据结构。

3. 使用%c自定义样式

对于面向UI的日志或当你想在控制台中突出显示特定内容时,可以使用%c指令配合CSS样式:

console.log('%c student', 'color: orange; font-weight: bold');

这在大型应用中特别有用,当你需要从视觉上区分不同日志时,比如高亮警告、步骤或关键状态更新。

4. 使用console.table()以表格形式显示对象

当处理对象数组(或具有多个相似键的单个对象)时,console.table()是一个视觉上更优的选择:

console.table([student, tutor]);

这会记录一个结构化的表格,包含行和列,便于扫描和比较值。当你处理API响应、用户列表或状态对象等数据集时,这非常理想。

5. 使用console.time()console.timeEnd()进行基准测试

性能分析在JavaScript中至关重要,特别是当循环或操作可能影响响应速度时。使用console.time()console.timeEnd()来测量执行时间:

console.time('loop');

let i = 0;
while (i < 1000000) {
i++;
}

console.timeEnd('loop');

// 输出类似:
loop: 5.384ms

这让你了解你的逻辑执行需要多长时间,有助于优化决策或发现代码中的瓶颈。

通过掌握这些增强的日志记录技巧,你不仅仅是打印数据——你是在传达意图,提高可读性,更智能地进行调试。这些工具是你JavaScript工具带的一部分,帮助你在开发工作流程中变得更高效、更有组织和更有效。

模板字符串:拥有超能力的字符串编写

使用+运算符进行字符串拼接的混乱日子已经一去不复返了。随着ES6引入的模板字符串,JavaScript为你提供了一种更干净、更具表现力的方式来构建字符串,特别是当涉及变量和表达式时。模板字符串用反引号而不是单引号或双引号括起来,它们支持插值、多行字符串甚至嵌入表达式。

让我们分解一下:

// 基本字符串插值
const name = 'Ray';
const age = 20;

console.log(`My name is ${name} and I am ${age} years old.`);

而不是这样写:

'My name is ' + name + ' and I am ' + age + ' years old.'

模板字符串让你使用${}直接将变量注入字符串。这不仅减少了混乱,还提高了可读性和可维护性。

// 多行字符串(不需要\n!)
const message = `Hello,
This is a multiline message,
Neatly written with template literals.`;

console.log(message);

以前,你必须使用\n或笨拙地连接行。使用反引号,你可以自然地跨行书写。

// 模板字符串中的表达式和函数调用
const price = 100;
const discount = 0.2;

console.log(`Final price after discount: ${price - (price * discount)} UGX`);

你甚至可以在里面调用函数:

function greet(name) {
return `Hello, ${name.toUpperCase()}!`;
}

console.log(`${greet('ray')}`);

模板字符串让你的代码更具表现力和更干净,特别是在涉及动态数据、API响应或日志输出的场景中。无论你是制作UI消息、生成报告还是调试输出,这个特性都为你的字符串增添了优雅和力量。

使用reduce()获取总计:从笨拙循环到干净逻辑

当处理数字或对象数组时,计算总数(无论是价格、分数还是物品数量)是很常见的。许多开发者陷入了使用冗长的for循环forEach()来完成这一任务的陷阱。虽然这些方法可行,但它们通常表现力较差且更难维护。reduce()方法提供了一个强大而优雅的替代方案,不仅能精简你的代码,还能增强其可读性和意图。

// 糟糕的代码——使用forEach()计算总计
const cart = [
{ item: 'Book', price: 15 },
{ item: 'Pen', price: 5 },
{ item: 'Notebook', price: 10 },
];

let total = 0;

cart.forEach(product => {
total += product.price;
});

console.log(`Total price: ${total} UGX`);

这种方法可行,但在大型代码库中显得冗长。你手动管理总数,这可能导致错误和副作用。

const cart = [
{ item: 'Book', price: 15 },
{ item: 'Pen', price: 5 },
{ item: 'Notebook', price: 10 },
];

const total = cart.reduce((acc, curr) => acc + curr.price, 0);

console.log(`Total price: ${total} UGX`);

以下是reduce()更优越的原因:

  • acc(累加器)从0开始(reduce()的第二个参数)。
  • 每次迭代时,它将curr.price加到acc上。
  • 循环结束后,你得到最终总数——干净、函数式且声明式。

你甚至可以使用箭头函数和默认值使其更具表现力:

const total = cart.reduce((sum, { price }) => sum + price, 0);

这个版本使用解构直接访问每个物品的价格,使代码更具可读性。

所以下次你在数组中求和时,放弃循环,改用reduce()。这是一种函数式编写逻辑的方式,不仅正确,而且干净强大。

理解展开运算符和数组合并

展开运算符(...)是ES6引入的最优雅和多功能特性之一。它允许你将可迭代对象(如数组或对象)的元素"展开"为单独的元素。这在合并数组、克隆或在函数调用中传递多个值时特别有用。

基础数组合并(干净现代的方式)

假设你有两个数组:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

这是使用concat()的老派方法:

const merged = arr1.concat(arr2);
console.log(merged); // [1, 2, 3, 4, 5, 6]

这可行,但与现代语法相比有点冗长且可读性较差。

这是使用展开运算符的现代方法:

const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

新方法更干净、更具表现力且更容易理解。你实际上是在说:"将arr1和arr2的值展开到一个新数组中。"

在有重复值的情况下,使用Set和展开运算符合并。

假设数组有重复值:

const arr1 = [1, 2, 3, 3];
const arr2 = [3, 4, 5, 6, 6];

如果我们直接合并它们:

const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 3, 3, 4, 5, 6, 6]

现在我们有了重复值。在许多情况下(如ID、标签等),我们想要唯一值。

所以为了移除重复值,我们可以使用Set和展开运算符。

const uniqueMerge = [...new Set([...arr1, ...arr2])];
console.log(uniqueMerge); // [1, 2, 3, 4, 5, 6]

给你一些背景,Set是一个内置对象,只存储唯一值。我们首先使用[...]合并数组。然后我们用new Set(...)包装它来过滤掉重复项,最后,我们将其展开回一个新数组。

使用展开运算符克隆数组

如果你想复制一个数组而不修改原始数组,使用展开运算符:

const original = [10, 20, 30];
const copy = [...original];

copy.push(40);
console.log(original); // [10, 20, 30] ✅ 未改变
console.log(copy); // [10, 20, 30, 40]

理解剩余运算符的相关性

剩余运算符(...)看起来与展开运算符完全一样,但它的工作方式相反。展开运算符展开项目,而剩余运算符将项目收集到单个数组或对象中。

它主要用于:

  1. 函数参数
  2. 数组解构
  3. 对象解构

1. 函数参数中的剩余运算符

有时,你不知道函数会接收多少个参数。与其手动列出它们,不如使用剩余运算符将它们打包起来。

function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(5, 10, 15, 20)); // 50

解释:

  • ...numbers将所有传递的参数收集到一个数组中。
  • 然后你可以像操作普通数组一样操作它(比如使用.reduce()将它们相加)。

2. 数组解构中的剩余运算符

当你想要单独获取一些值并收集其余值时,剩余运算符很方便。

const colors = ['red', 'green', 'blue', 'yellow'];

const [primary, secondary, ...others] = colors;

console.log(primary); // red
console.log(secondary); // green
console.log(others); // ['blue', 'yellow']

解释:

  • 前两个值被提取出来。
  • 剩余运算符将其他所有内容收集到一个新数组others中。

3. 对象解构中的剩余运算符

你也可以从对象中提取特定属性并收集剩余的属性。

const user = {
id: 1,
name: 'Ray',
age: 25,
role: 'developer'
};

const { name, ...rest } = user;
console.log(name); // 'Ray'
console.log(rest); // { id: 1, age: 25, role: 'developer' }

解释: 我们提取出name属性。 剩余运算符将剩余的键值对收集到rest中。

虽然剩余(...)和展开(...)运算符在语法上看起来相同,但它们的行为和目的根本不同。展开运算符用于将数组或对象展开为单独的元素或属性。你通常会在向函数传递参数或组合数组和对象时看到它。相比之下,剩余运算符做相反的事情。它用于将多个值收集到单个数组或对象中。它通常应用于函数参数中以接受不定数量的参数,或在解构中收集剩余值。可以这样想:展开向外扩展,将结构分开,而剩余向内收集,将它们捆绑在一起。这种微妙但强大的区别是编写灵活且干净的JavaScript代码的关键。

理解JavaScript中的对象数据提取

在JavaScript中,对象解构是一种简写语法,允许你从对象(或数组中的元素)中提取属性到独立的变量中。这使你的代码更干净并减少冗余。

让我们看一个基本对象:

const me = {
name: 'Ray',
age: 20,
likes: ['football', 'racing']
};

传统方式(不够优雅)

如果你想访问值,通常会这样做:

console.log(me.age); // 20
console.log(me.likes[0]); // 'football'

虽然这完全没问题,但它可能变得冗长或重复,特别是当你需要多次引用同一属性时。

现在让我们使用解构来编写更干净的代码

与其重复写me.age,你可以这样解构:

const { age } = me;
console.log(age); // 20

你也可以一次提取多个属性:

const { name, likes } = me;
console.log(name); // 'Ray'
console.log(likes); // ['football', 'racing']

解构对象中的数组

因为likes是一个数组,我们也可以解构它:

const { likes: [first, second] } = me;

console.log(first); // 'football'
console.log(second); // 'racing'

注意我们如何使用别名。我们没有提取likes数组本身,而是直接将其元素解包到firstsecond中。

空值合并运算符(??)

空值合并运算符(??)用于在变量为nullundefined时提供默认值。当你只想为真正的"空"值回退,而不是为0''false等假值时,它特别有用。

没有空值合并:

const age = 0;
const result = age || 18;
console.log(result); // 18 ❌ (0是假值,所以回退)

使用空值合并:

const age = 0;
const result = age ?? 18;
console.log(result); // 0 ✅

解释:

  • || 对任何假值(0false''nullundefined)都会回退。
  • ?? 只对nullundefined回退。

使用+号将字符串转换为整数

在JavaScript中,一元加号(+)运算符可用于快速将字符串转换为数字。

传统方法:

const numStr = '42';
const num = parseInt(numStr);
console.log(num); // 42

使用+的简写:

const numStr = '42';
const num = +numStr;
console.log(num); // 42

当你确定字符串包含有效数字时,这是一个快速且可读的技巧。如果不包含,结果将是NaN

看下面的代码:

console.log(+'hello'); // NaN

可选链式调用(?.)

可选链式调用让你可以安全地访问深层嵌套的对象属性,而不必检查每一级是否存在。如果引用是nullundefined,它会返回undefined而不是抛出错误。

没有可选链式调用:

const user = {};
console.log(user.profile.name); // ❌ 错误:无法读取未定义的属性'name'

使用可选链式调用:

const user = {};
console.log(user.profile?.name); // ✅ undefined

你也可以在函数中使用它:

user.sayHi?.(); // 仅当sayHi存在时调用

这对于处理API数据非常有用,其中某些字段可能缺失。

三元运算符

三元运算符是if/else的简写。它遵循以下语法:

condition ? valueIfTrue : valueIfFalse;

给你一些背景,看下面的代码:

const age = 20;
const canVote = age >= 18 ? 'Yes' : 'No';
console.log(canVote); // "Yes"

这等同于:

let canVote;
if (age >= 18) {
canVote = 'Yes';
} else {
canVote = 'No';
}

使用||设置默认值

逻辑或(||)运算符可用于在左侧为假值时分配默认值:

const name = userInput || 'Guest';

如果userInput是假值(如nullundefined''),name将是'Guest'

逻辑短路

JavaScript还允许你使用&&运算符编写没有else的简短if语句。

condition && expression;

例如:

const isLoggedIn = true;
isLoggedIn && console.log('User is logged in');

只有当isLoggedIntrue时才会打印。等同于:

if (isLoggedIn) {
console.log('User is logged in');
}

结论

掌握JavaScript的关键在于编写优雅、高效且富有表现力的代码。这些技巧,从更智能的console.log()实践和模板字符串,到解构、展开/剩余运算符的微妙力量以及简短的条件逻辑,都将帮助你将代码从功能性提升到强大。

把这些模式看作你武术腰带上的工具,每一个都能磨砺你编写更干净、更快且更易维护代码的能力。无论你是像专业人士一样使用console.table()console.time()进行调试,还是以手术般的精度合并数组,你构建的每一项技能都会让你的JavaScript功夫更强大。

现在,像大师一样去编写代码吧。未来的你和你的团队成员会为此感谢你。

想更深入地探索JavaScript的可能性吗?阅读Andela的完整指南"掌握JavaScript代理和反射的实际应用"

原文链接:thenewstack.io/javascript-… 作者:Zziwa Raymond Ian