- 教程
【免费下载链接】jstips
This is about useful JS tips!
本篇是 jstips 仓库中 2016-10-28-three-useful-hacks.md(第 60 期技巧)的深度解读。原文档分享了三个高频、轻量的语法糖:用
slice(-n)从后向前读取数组、用&&短路执行条件函数、用||为变量设置默认值。读完本文,你将理解这三个技巧的底层运行原理、边界情况与常见陷阱,并能结合函数默认参数、空值合并运算符等现代写法做出正确的选型,在日常业务代码中写出更简洁、更不易出错的表达式。
三个技巧解决什么问题
JavaScript 开发者每天都在处理三件小事:取数组元素、做条件分支、赋默认值。这三个操作本身并不难,但写法却有好坏之分。jstips 第 60 期(英文原版见 _posts/en/javascript/2016-10-28-three-useful-hacks.md)给出的三个写法,共同特点是:
- 更短:用表达式替代语句,减少括号与换行;
- 更聚焦:把"取尾部元素""满足条件才执行""无值则用默认值"这类意图直接表达出来;
- 依赖语言内建语义:不引入任何库,基于
Array.prototype.slice、&&、||的自带行为。
下面逐一展开,并在每个技巧中补充原理、边界与仓库内其他期数的佐证。
技巧一:用slice(-n)从后向前获取数组元素
原文档给出的代码:
var newArray = [1, 2, 3, 4] console.log(newArray.slice(-1)) // [4] console.log(newArray.slice(-2)) // [3, 4] console.log(newArray.slice(-3)) // [2, 3, 4] console.log(newArray.slice(-4)) // [1, 2, 3, 4]为什么-n表示"从尾部开始"
Array.prototype.slice(begin, end)返回从begin到end(不含)之间的新数组。当传入的索引为负数时,语言规范会将其换算为length + 索引,例如slice(-1)等价于slice(3),slice(-2)等价于slice(2)。因此:
slice(-1)→ 取最后 1 个元素[4];slice(-2)→ 取最后 2 个元素[3, 4];slice(-n)的通式即"从倒数第 n 个元素开始,一直取到数组末尾"。
边界情况与等价写法
n超过数组长度:例如对[1, 2, 3, 4]执行slice(-10),换算后是负数索引,会被当作0处理,返回整个数组[1, 2, 3, 4],不会报错;n为 0:slice(-0)即slice(0),返回完整副本,注意不要用它来"取 0 个尾部元素";slice是非破坏性的:它返回新数组,原数组newArray保持不变。这点与pop()(取出并移除最后一个元素)、length = 0(清空数组)完全不同——关于后者,可参考仓库中 2016-01-22-two-ways-to-empty-an-array.md 对list = []与list.length = 0内存语义差异的剖析。
实战场景
- 读取最近 N 条记录:如聊天列表取最近 5 条,
messages.slice(-5); - 日志尾部查看:
logs.slice(-50)只展示最新 50 行,避免复制整个数组; - 环形/滚动数据结构:配合
concat做分页式尾部追加,可参考仓库中 2016-02-06-deduplicate-an-array.md 对数组复制与引用语义的讨论,理解为什么非破坏性操作更适合数据处理链路。
技巧二:短路条件句condition && dosomething()
原文档给出对比写法:
// 常规写法 if (condition) { dosomething() } // 短路写法 condition && dosomething()短路求值的本质
&&是短路求值运算符:只有当第一个操作数无法决定整个表达式的结果时,才会对第二个操作数求值。具体来说:
- 若
condition为 falsy,&&的结果已经确定为该 falsy 值,dosomething()根本不会被调用; - 若
condition为 truthy,才继续求值并调用dosomething()。
这正是"当条件为真时才执行"的语义,只不过把if语句压成了一行表达式。仓库中的 2016-01-27-short-circuit-evaluation-in-js.md 对这一机制有完整讲解:当&&第一个操作数为false时结果必为false,当||第一个操作数为true时结果必为true,因此第二个操作数可以跳过。
注意:&&返回的是操作数,而不是布尔值
condition && dosomething()的返回值分两种情况:condition 为 falsy 时返回 condition 本身,否则返回dosomething()的返回值。因此:
- 不要把这个表达式的结果直接当作"布尔值"使用;
- 如果
dosomething()返回undefined,整个表达式结果也是undefined; - 若要把结果统一成布尔值,可叠加
!!,即!!(condition && dosomething())。!!的 truthy/falsy 强制转换规则参见仓库中 2016-01-30-converting-truthy-falsy-values-to-boolean.md。
两个高频变体
1. 安全调用,避免 "Cannot read property of undefined"
&&常被用来在对象可能为空时安全访问属性,这正是 2016-01-27-short-circuit-evaluation-in-js.md 中演示的经典模式:
var dog = { bark: function() { console.log('Woof Woof'); } }; dog.bark(); // dog 未定义时会抛错 dog && dog.bark(); // 仅当 dog 存在时才调用,安全2. 回调函数保护
Node.js 风格的函数中,回调参数可能为空,直接用callback(err)会抛 TypeError。用短路写法即可优雅兜底,这一模式在仓库的 2016-05-06-use-optional-arguments.md 中反复出现:
if (err) { return callback && callback(err); }与if语句的取舍
condition && dosomething()适合单条、无返回值依赖的调用,让代码更紧凑;- 当需要多个语句、
else分支或较复杂的逻辑时,应回归if语句,避免可读性下降; - 条件为
false时若你本意是"执行另一个函数",可写成!condition && dosomethingElse(),但两个并列的短路表达式不如if/else直观。
技巧三:用||为变量设置默认值
原文档给出的代码:
var a console.log(a) // undefined a = a || 'default value' console.log(a) // default value a = a || 'new value' console.log(a) // default value运行原理
||同样遵循短路求值:若第一个操作数为 truthy,直接返回它,不再看第二个;若第一个操作数为 falsy,则返回第二个操作数。于是a = a || 'default value'的含义是:若a已经有值,保持原值;若a为空(falsy),则赋默认值。由于'default value'是 truthy,第二次a = a || 'new value'中a已非空,结果仍是'default value',原文档的输出恰好印证了这一点。
最大的陷阱:falsy 值不止undefined
||判断的是 truthy/falsy,而不是"是否为 null/undefined"。因此下面这些合法且常见的值也会被默认值替换:
var count = 0; count = count || 10; // 结果变成 10,而你可能期望保留 0 var name = ''; name = name || '匿名'; // 空字符串被替换 var ratio = NaN; ratio = ratio || 1; // NaN 被替换 var flag = false; flag = flag || true; // false 被替换成 true,逻辑反转!falsy 值的完整集合是false、0、-0、0n、''、null、undefined、NaN(可对照 2016-01-30-converting-truthy-falsy-values-to-boolean.md 中的!!示例)。所以当业务上"0、空字符串、false"都是合法值时,||并不合适。
更精准的替代方案
- 函数参数默认值(ES6):仅在参数为
undefined时生效,0、''、false会原样保留:
function greet(name = '访客') { return 'Hello, ' + name; } greet(); // Hello, 访客 greet(''); // Hello, greet('Tom'); // Hello, Tom- 空值合并运算符
??(ES2020):只对null和undefined兜底,适合"数字 0、空字符串是有效值"的场景:
var count = 0; count = count ?? 10; // 0,保留 var maybeNull = null; maybeNull = maybeNull ?? 10; // 10- 需要区分
undefined与null时,可参考仓库 2016-01-05-differences-between-undefined-and-null.md 的总结:undefined表示"未赋值",null表示"显式的空值",两者用==相等但用===不等。
函数入参默认值的经典用法
||给函数参数设默认值是最常见的实战场景,仓库 2016-01-27-short-circuit-evaluation-in-js.md 中就有完整示例:
function theSameOldFoo(name){ name = name || 'Bar'; console.log("My best friend's name is " + name); } theSameOldFoo(); // My best friend's name is Bar theSameOldFoo('Bhaskar'); // My best friend's name is Bhaskar注意同样的陷阱:如果name的合法值可能是空字符串或0,请改用上面的 ES6 默认参数或??。
三个技巧对照小结
| 技巧 | 写法 | 适用场景 | 必须注意 |
|---|---|---|---|
| 反向取数组 | arr.slice(-n) | 取尾部 N 个元素、最近记录 | 非破坏性;n=0返回整个副本 |
| 短路条件执行 | condition && fn() | 条件为真才执行、安全访问、回调保护 | 表达式结果是操作数而非布尔值 |
||设默认值 | a = a \|\| 'default' | 参数/变量为空时兜底 | 0、''、false、NaN都会被替换 |
从仓库结构看,这三个技巧并非孤例:短路求值(第 27 期)、truthy/falsy 转换(第 30 期)、可选参数与回调保护(第 54 期)等文档都在围绕同一套"表达式即控制流"的 JavaScript 内建语义展开,说明它们是高频且值得熟练掌握的基础能力。建议在团队代码中为这三个写法约定统一的边界(如:业务上允许0/空字符串时禁用||兜底),避免语法糖带来的隐式 bug。
延伸阅读(仓库内)
- 2016-01-27-short-circuit-evaluation-in-js.md:短路求值完整原理与安全访问示例
- 2016-01-30-converting-truthy-falsy-values-to-boolean.md:truthy/falsy 值与
!!强制转换 - 2016-01-05-differences-between-undefined-and-null.md:
undefined与null的判断与区别 - 2016-05-06-use-optional-arguments.md:可选参数与
callback && callback(err)的实战模式 - 2016-01-22-two-ways-to-empty-an-array.md:数组引用语义与
length = 0的说明 - 2016-02-06-deduplicate-an-array.md:基于
filter、Set的数组去重与引用语义分析
- 教程
【免费下载链接】jstips
This is about useful JS tips!
相关推荐
jstips 第 60 期:三个提升开发效率的 JavaScript 语法糖技巧(slice 负索引、短路求值、`||` 默认值)
jstips 第 60 期:三个提升开发效率的 JavaScript 语法糖技巧(slice 负索引、短路求值、 || 默认值) 本篇技术指南源自 jstips
教程jstips 实用技巧:三个基于 ES6 扩展运算符的 JavaScript 数组 Hack
jstips 实用技巧:三个基于 ES6 扩展运算符的 JavaScript 数组 Hack 数组(Array)在 JavaScript 中无处不在,而 ECM
教程TwitchDropsMiner终极指南:自动化获取游戏奖励的专业解决方案
TwitchDropsMiner终极指南:自动化获取游戏奖励的专业解决方案 厌倦了为了获取Twitch游戏掉落奖励而长时间观看直播? TwitchDropsMi
桌面应用工作流自动化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考