特性测试

什么是特性测试?它是一种由你运行来判定一个特性是否可用的测试。有些时候,这种测试不仅是为了判定存在性,还是为判定对特定行为的适应性 —— 特性可能存在但有bug。

这是一种元编程技术 —— 测试你程序将要运行的环境然后判定你的程序应当如何动作。

在JS中特性测试最常见的用法是检测一个API的存在性,而且如果它不存在,就定义一个填补(见第一章)。例如:

  1. if (!Number.isNaN) {
  2. Number.isNaN = function(x) {
  3. return x !== x;
  4. };
  5. }

在这个代码段中的if语句就是一个元编程:我们探测我们的程序和它的运行时环境,来判定我们是否和如何进行后续处理。

但是如何测试一个涉及新语法的特性呢?

你可能会尝试这样的东西:

  1. try {
  2. a = () => {};
  3. ARROW_FUNCS_ENABLED = true;
  4. }
  5. catch (err) {
  6. ARROW_FUNCS_ENABLED = false;
  7. }

不幸的是,这不能工作,因为我们的JS程序是要被编译的。因此,如果引擎还没有支持ES6箭头函数的话,它就会在() => {}语法的地方熄火。你程序中的语法错误会阻止它的运行,进而阻止你程序根据特性是否被支持而进行后续的不同相应。

为了围绕语法相关的特性进行特性测试的元编程,我们需要一个方法将测试与我们程序将要通过的初始编译步骤隔离开。举例来说,如果我们能够将进行测试的代码存储在一个字符串中,之后JS引擎默认地将不会尝试编译这个字符串中的内容,直到我们要求它这么做。

你的思路是不是跳到了使用eval(..)

别这么着急。看看本系列的 作用域与闭包 来了解一下为什么eval(..)是一个坏主意。但是有另外一个缺陷较少的选项:Function(..)构造器。

考虑如下代码:

  1. try {
  2. new Function( "( () => {} )" );
  3. ARROW_FUNCS_ENABLED = true;
  4. }
  5. catch (err) {
  6. ARROW_FUNCS_ENABLED = false;
  7. }

好了,现在我们判定一个像箭头函数这样的特性是否 被当前的引擎所编译来进行元编程。你可能会想知道,我们要用这种信息做什么?

检查API的存在性,并定义后备的API填补,对于特性检测成功或失败来说都是一条明确的道路。但是对于从ARROW_FUNCS_ENABLEDtrue还是false中得到的信息来说,我们能对它做什么呢?

因为如果引擎不支持一种特性,它的语法就不能出现在一个文件中,所以你不能在这个文件中定义使用这种语法的函数。

你所能做的是,使用测试来判定你应当加载哪一组JS文件。例如,如果在你的JS应用程序中的启动装置中有一组这样的特性测试,那么它就可以测试环境来判定你的ES6代码是否可以直接加载运行,或者你是否需要加载一个代码的转译版本(参见第一章)。

这种技术称为 分割投递

事实表明,你使用ES6编写的JS程序有时可以在ES6+浏览器中完全“原生地”运行,但是另一些时候需要在前ES6浏览器中运行转译版本。如果你总是加载并使用转译代码,即便是在新的ES6兼容环境中,至少是有些情况下你运行的也是次优的代码。这并不理想。

分割投递更加复杂和精巧,但对于你编写的代码和你的程序所必须在其中运行的浏览器支持的特性之间,它代表一种更加成熟和健壮的桥接方式。

FeatureTests.io

为所有的ES6+语法以及语义行为定义特性测试,是一项你可能不想自己解决的艰巨任务。因为这些测试要求动态编译(new Function(..)),这会产生不幸的性能损耗。

另外,在每次你的应用运行时都执行这些测试可能是一种浪费,因为平均来说一个用户的浏览器在几周之内至多只会更新一次,而即使是这样,新特性也不一定会在每次更新中都出现。

最终,管理一个对你特定代码库进行的特性测试列表 —— 你的程序将很少用到ES6的全部 —— 是很容易失控而且易错的。

https://featuretests.io”的“特性测试服务”为这种挫折提供了解决方案。

你可以将这个服务的库加载到你的页面中,而它会加载最新的测试定义并运行所有的特性测试。在可能的情况下,它将使用Web Worker的后台处理中这样做,以降低性能上的开销。它还会使用LocalStorage持久化来缓存测试的结果 —— 以一种可以被所有你访问的使用这个服务的站点所共享的方式,这将及大地降低测试需要在每个浏览器实例上运行的频度。

你可以在每一个用户的浏览器上进行运行时特性测试,而且你可以使用这些测试结果动态地向用户传递最适合他们环境的代码(不多也不少)。

另外,这个服务还提供工具和API来扫描你的文件以判定你需要什么特性,这样你就能够完全自动化你的分割投递构建过程。

对ES6的所有以及未来的部分进行特性测试,以确保对于任何给定的环境都只有最佳的代码会被加载和运行 —— FeatureTests.io使这成为可能。