
本文深入解析为何 scores?.[player]++ && scores[player] = 1 会报语法错误,并阐明可选链(?.)不能用于赋值左侧的根本原因,同时提供安全、简洁且符合现代 js 规范的替代方案。
本文深入解析为何 scores?.[player]++ && scores[player] = 1 会报语法错误,并阐明可选链(?.)不能用于赋值左侧的根本原因,同时提供安全、简洁且符合现代 js 规范的替代方案。
在 JavaScript 中,可选链操作符(?.)是一项强大的安全访问特性,用于避免因访问 null 或 undefined 值的属性而抛出运行时错误。但一个关键限制常被初学者忽略:可选链表达式不可作为赋值操作的左值(LHS)。这意味着你无法对 obj?.prop、arr?.[i] 或 obj?.[key] 执行 ++、+=、= 等修改操作。
回到你的代码:
const scores = {};
for (const player of Object.values(game.scored)) {
scores?.[player]++ && scores[player] = 1; // ❌ 语法错误:Invalid left-hand side expression
}该语句存在双重问题:
- 语法非法:scores?.[player]++ 尝试对一个可能为 undefined 的表达式执行后置递增——这在 JS 语法层面被禁止。引擎无法将 undefined++ 解析为有效赋值目标;
- 逻辑错位:scores?.[player] 检查的是 scores 是否为 null/undefined,而非 scores[player] 是否已存在。而你明确初始化了 scores = {},因此 scores?.[player] 永远等价于 scores[player],可选链在此处完全冗余,反而掩盖了真实需求——判断对象是否已包含某个键。
✅ 正确的目标应是:若 scores[player] 已存在(即 != null 且有定义),则累加;否则初始化为 1。以下是几种专业、推荐的实现方式:
立即学习“Java免费学习笔记(深入)”;
✅ 推荐方案一:空值合并赋值(??=)+ 自增(最简洁现代)
const scores = {};
for (const player of Object.values(game.scored)) {
scores[player] ??= 0; // 若未定义,则设为 0
scores[player]++; // 再递增
}
// 或合并为一行(语义清晰,无副作用):
// scores[player] = (scores[player] ?? 0) + 1;✅ 推荐方案二:逻辑或赋值(||=)——适用于“falsy 值即视为未设置”
for (const player of Object.values(game.scored)) {
scores[player] ||= 0;
scores[player]++;
}⚠️ 注意:||= 在 scores[player] 为 0、''、false 时也会重置,因此仅当计数器不会出现 0 以外的 falsy 值时才安全。
✅ 推荐方案三:in 操作符显式检测(语义最精确)
for (const player of Object.values(game.scored)) {
if (player in scores) {
scores[player]++;
} else {
scores[player] = 1;
}
}✅ 推荐方案四:Map(适合高频增删查场景,避免原型污染)
const scores = new Map();
for (const player of Object.values(game.scored)) {
scores.set(player, (scores.get(player) ?? 0) + 1);
}
// 获取值:scores.get('Alice')⚠️ 关键注意事项总结:
- 可选链 ?. 仅用于读取(RHS),不可用于写入(LHS):obj?.prop = val、arr?.[i]++ 均非法;
- ?. 检查的是对象本身是否为空,不是其属性是否存在;
- 计数类逻辑优先使用 ??=(空值合并)而非 ||=,避免 0 被误判;
- 对于纯计数场景,scores[key] = (scores[key] ?? 0) + 1 是最直观、无副作用的单行解法;
- 若需强类型或避免意外属性,Map 是更健壮的选择。
掌握这些原则,你就能避开陷阱,写出既安全又地道的现代 JavaScript 代码。










