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

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

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

我们将涵盖以下内容:

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

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

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

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

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

javascript 复制代码
const student = { name: 'Ray', age: 20 };
const tutor = { name: 'Peter', age: 40 };

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

javascript 复制代码
console.log(student);
console.log(tutor);

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

2. 计算属性日志记录

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

javascript 复制代码
console.log({ student, tutor });

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

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

3. 使用%c自定义样式

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

javascript 复制代码
console.log('%c student', 'color: orange; font-weight: bold');

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

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

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

javascript 复制代码
console.table([student, tutor]);

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

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

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

javascript 复制代码
console.time('loop');

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

console.timeEnd('loop');

// 输出类似:
loop: 5.384ms

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

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

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

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

让我们分解一下:

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.'

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

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

console.log(message);

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

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

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

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

javascript 复制代码
function greet(name) {
return `Hello, ${name.toUpperCase()}!`;
}

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

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

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

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

javascript 复制代码
// 糟糕的代码------使用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`);

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

javascript 复制代码
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上。
  • 循环结束后,你得到最终总数------干净、函数式且声明式。

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

javascript 复制代码
const total = cart.reduce((sum, { price }) => sum + price, 0);

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

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

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

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

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

假设你有两个数组:

javascript 复制代码
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

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

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

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

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

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

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

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

假设数组有重复值:

javascript 复制代码
const arr1 = [1, 2, 3, 3];
const arr2 = [3, 4, 5, 6, 6];

如果我们直接合并它们:

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

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

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

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

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

使用展开运算符克隆数组

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

javascript 复制代码
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. 函数参数中的剩余运算符

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

javascript 复制代码
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. 数组解构中的剩余运算符

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

javascript 复制代码
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. 对象解构中的剩余运算符

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

javascript 复制代码
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中,对象解构是一种简写语法,允许你从对象(或数组中的元素)中提取属性到独立的变量中。这使你的代码更干净并减少冗余。

让我们看一个基本对象:

javascript 复制代码
const me = {
name: 'Ray',
age: 20,
likes: ['football', 'racing']
};

传统方式(不够优雅)

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

javascript 复制代码
console.log(me.age); // 20
console.log(me.likes[0]); // 'football'

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

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

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

javascript 复制代码
const { age } = me;
console.log(age); // 20

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

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

解构对象中的数组

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

javascript 复制代码
const { likes: [first, second] } = me;

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

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

空值合并运算符(??)

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

没有空值合并:

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

使用空值合并:

javascript 复制代码
const age = 0;
const result = age ?? 18;
console.log(result); // 0 ✅

解释:

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

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

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

传统方法:

javascript 复制代码
const numStr = '42';
const num = parseInt(numStr);
console.log(num); // 42

使用+的简写:

javascript 复制代码
const numStr = '42';
const num = +numStr;
console.log(num); // 42

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

看下面的代码:

javascript 复制代码
console.log(+'hello'); // NaN

可选链式调用(?.)

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

没有可选链式调用:

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

使用可选链式调用:

javascript 复制代码
const user = {};
console.log(user.profile?.name); // ✅ undefined

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

javascript 复制代码
user.sayHi?.(); // 仅当sayHi存在时调用

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

三元运算符

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

javascript 复制代码
condition ? valueIfTrue : valueIfFalse;

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

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

这等同于:

javascript 复制代码
let canVote;
if (age >= 18) {
canVote = 'Yes';
} else {
canVote = 'No';
}

使用||设置默认值

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

javascript 复制代码
const name = userInput || 'Guest';

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

逻辑短路

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

condition && expression;

例如:

javascript 复制代码
const isLoggedIn = true;
isLoggedIn && console.log('User is logged in');

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

javascript 复制代码
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

相关推荐
gnip16 小时前
链式调用和延迟执行
前端·javascript
SoaringHeart16 小时前
Flutter组件封装:页面点击事件拦截
前端·flutter
杨天天.16 小时前
小程序原生实现音频播放器,下一首上一首切换,拖动进度条等功能
前端·javascript·小程序·音视频
Dragon Wu16 小时前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
Jinuss16 小时前
Vue3源码reactivity响应式篇之watch实现
前端·vue3
YU大宗师16 小时前
React面试题
前端·javascript·react.js
木兮xg16 小时前
react基础篇
前端·react.js·前端框架
ssshooter17 小时前
你知道怎么用 pnpm 临时给某个库打补丁吗?
前端·面试·npm
IT利刃出鞘17 小时前
HTML--最简的二级菜单页面
前端·html
yume_sibai17 小时前
HTML HTML基础(4)
前端·html