use case of switch

JavaScript

在《JavaScript: The Good Parts》里,作者并不赞成 switch 语句的使用(主要是因为 fall-through 的情况很容易造成错误)。然而在实际的代码里,还是有不少地方可以看到 switch 的使用。目的各不相同,有不少可以借鉴的地方。

默认值设置

React 的 Scheduler 中,有这样一段代码:

switch (priorityLevel) {
  case ImmediatePriority:
  case UserBlockingPriority:
  case NormalPriority:
  case LowPriority:
  case IdlePriority:
    break;
  default:
    priorityLevel = NormalPriority;
}

不失为设置默认值的一种写法,看上去比使用 if 来得更明确一些:

if (
  priorityLevel !== ImmediatePriority &&
  priorityLevel !== UserBlockingPriority &&
  priorityLevel !== NormalPriority &&
  priorityLevel !== LowPriority &&
  priorityLevel !== IdlePriority
) {
  priorityLevel = NormalPriority;
}

防止代码篡改的判定

上面的需求,也很容易写成下面这种数组的方案:

const allowedValues = [
  ImmediatePriority,
  UserBlockingPriority,
  NormalPriority,
  LowPriority,
  IdlePriority,
];
const isNot = value => comparedTo => value !== comparedTo;
if (allowedValues.every(isNot(priorityLevel))) {
  priorityLevel = NormalPriority;
}

然而,这样的代码方式,可能存在被入侵的危险。不论是上面例子中的 every 函数,还是用 Array.prototype 上的任意函数,都有被篡改的可能性。如果其他地方的代码修改了 Array.prototype.every 的行为,让这里的返回值发生了变化,那么代码最终就会产生意料之外的行为。

在 Scheduler 中当然不需要考虑这个问题,但是在其他的应用场景下,这可能是不得不考虑的问题。举例来说,如果一个 Web 应用允许第三方脚本的运行,同时自身有对数据进行白名单检查的需求,那么就只能使用 switch 硬编码所有的情况,而不能使用数组或者对象,否则第三方的脚本有可能对最终的行为做篡改。

Microsoft Teams 的代码里,就有类似的应用场景(见 extracted/lib/renderer/preload_sandbox.js):

const isChannelAllowed = (channel) => {
  // ...
  let isAllowed = false;
  // IMPORTANT - the allowList must be a hardcorded switch statement.
  // Array and object methods can be overridden and forced to return true.
  switch (channel) {
    case xxx:
    // ...
    case zzz:
      isAllowed = true;
      break;
    default:
      isAllowed = false;
      break;
  }
}