【问题标题】:Javascript ES6 cross-browser detectionJavascript ES6 跨浏览器检测
【发布时间】:2015-05-16 18:50:46
【问题描述】:

如何查看浏览器的 Javascript 引擎版本和对 ECMAScript 6 的支持?

我使用navigator.appVersion 只是为了知道浏览器的版本,而不是引擎的版本。

【问题讨论】:

  • 你为什么想知道这个?
  • 没有标准属性,您可以简单地阅读以获取该信息。您可以构建字典或已知平台或使用功能测试。
  • 如果您想要新语法,请使用输出与旧版本兼容的 javascript 的预编译器。如果您想要新功能,请使用功能检测,就像 Marco Bonelli 推荐的那样。
  • 你可以使用feature-detect-es6库..

标签: javascript ecmascript-6


【解决方案1】:

特征检测

我建议您使用特征检测,而不是使用启发式方法检测浏览器的引擎。为此,您可以简单地try {..} catch (e) {...} 语句中包装一些代码,或使用一些if (...) 语句

例如:

function check() {
    if (typeof SpecialObject == "undefined") return false;
    try { specialFunction(); }
    catch (e) { return false; }

    return true;
}

if (check()) {
    // Use SpecialObject and specialFunction
} else {
    // You cannot use them :(
}

为什么特征检测优于浏览器/引擎检测?

在大多数情况下,有多种原因使特征检测成为最佳选择:

  • 您不必依赖浏览器的版本、引擎或细节,也不必使用难以实施且相当狡猾的启发式方法检测它们。

  • 您不会陷入有关浏览器/引擎规格检测的错误。

  • 您不必担心特定于浏览器的功能:例如WebKit 浏览器的规格与其他浏览器不同。

  • 您可以确定,一旦检测到某个功能,您就可以使用它。

这些是恕我直言使特征检测成为最佳方法的主要原因。

特征检测+回退

使用特征检测时,当您不确定可以/不能使用哪些功能时,一种非常聪明的工作方式包括几个特征检测和后续回退到更基本的方法(甚至从头开始创建这些方法),以防您想要使用的功能不受支持。

带有回退的特征检测的简单示例可以应用于window.requestAnimationFrame 功能,并非所有浏览器都支持该功能,并且根据您正在使用的浏览器有几个不同的前缀在。在这种情况下,您可以像这样轻松检测和回退

requestAnimationFrame = 
   window.requestAnimationFrame       // Standard name
|| window.webkitRequestAnimationFrame // Fallback to webkit- (old versions of Chrome or Safari)
|| window.mozRequestAnimationFrame    // Fallback to moz- (Mozilla Firefox)
|| false;                             // Feature not supported :(

// Same goes for cancelAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || false;

if (!requestAnimationFrame) {
    // Not supported? Build it by yourself!
    requestAnimationFrame = function(callback) {
        return setTimeout(callback, 0);
    }

    // No requestAnim. means no cancelAnim. Built that too.
    cancelAnimationFrame = function(id) {
        clearTimeout(id);
    }
}

// Now you can use requestAnimationFrame 
// No matter which browser you're running
var animationID = requestAnimationFrame(myBeautifulFunction);

ECMAScript 6 (Harmony) 特征检测

现在,真正的问题:如果你想检测对 ES6 的支持,你将无法像我上面所说的那样行事,因为 ES6 的相关范围features 基于新语法和私有词,如果在 ES5 中使用会抛出 SyntaxError,这意味着 编写包含 ES5 和 ES6 的脚本是不可能的!

这里有一个例子来说明这个问题;下面的sn-p不生效,执行前会被阻塞,因为包含非法语法。

function check() {
    "use strict";

    try { eval("var foo = (x)=>x+1"); }
    catch (e) { return false; }
    return true;
}

if (check()) {
    var bar = (arg) => { return arg; }
    // THIS LINE will always throw a SyntaxError in ES5
    // even before checking for ES6
    // because it contains illegal syntax.
} else {
    var bar = function(arg) { return arg; }
}

现在,由于您不能在同一个脚本中同时检查和执行 ES6,您必须编写两个不同的脚本:一个仅使用 ES5,另一个包含 ES6 功能.使用两种不同的脚本,您将能够仅在支持 ES6 的情况下导入一个,并且不会导致 SyntaxErrors 被抛出。

ES6检测与条件执行示例

现在让我们做一个更相关的例子,假设你想在你的 ES6 脚本中使用这些特性:

  • 新的Symbol 对象
  • 使用 class 关键字构建的类
  • 箭头 ((...)=>{...}) 函数

注意:特征检测新引入的语法(如箭头函数)只能使用eval() 函数完成 或其他等效项(例如Function()),因为编写无效语法会在脚本执行之前停止脚本。这也是您不能使用if 语句来检测类和箭头函数的原因:这些功能与关键字和语法有关,因此包裹在try {...} catch (e) {...} 块中的eval(...) 可以正常工作。

所以,来看看真正的代码:

  • HTML 标记:

    <html>
        <head>
            <script src="es5script.js"></script>
        </head>
        <body>
            <!-- ... -->
        </body>
    </html>
    
  • es5script.js 脚本中的代码:

    function check() {
        "use strict";
    
        if (typeof Symbol == "undefined") return false;
        try {
            eval("class Foo {}");
            eval("var bar = (x) => x+1");
        } catch (e) { return false; }
    
        return true;
    }
    
    if (check()) {
        // The engine supports ES6 features you want to use
        var s = document.createElement('script');
        s.src = "es6script.js";
        document.head.appendChild(s);
    } else {
        // The engine doesn't support those ES6 features
        // Use the boring ES5 :(
    }
    
  • es6script.js 中的代码:

    // Just for example...
    "use strict";
    
    class Car { // yay!
       constructor(speed) {
           this.speed = speed;
       }
    }
    
    var foo = Symbol('foo'); // wohoo!
    var bar = new Car(320);  // blaze it!
    var baz = (name) => { alert('Hello ' + name + '!'); }; // so cool!
    

浏览器/引擎检测

就像我上面所说的,在编写一些 JavaScript 脚本时,浏览器和引擎检测并不是最佳实践。我会给你一些关于这个话题的背景,只是为了不让我的话成为“随机的个人观点”。

引用 MDN 文档 [link]:

当考虑使用用户代理字符串来检测正在使用的浏览器时,第一步是尽可能避免使用它。首先尝试确定您为什么要这样做。

[...] 您是否要检查特定功能是否存在? 您的站点需要使用某些浏览器尚不支持的特定 Web 功能,并且您希望将这些用户发送到功能较少但您知道可以使用的较旧的 Web 站点。这是使用用户代理检测的最糟糕的原因,因为最终所有其他浏览器都会赶上。您应该尽量避免在这种情况下使用用户代理嗅探,并进行特征检测

另外,您说您使用 navigator.appVersion,但考虑使用另一种方法,因为该方法以及许多其他导航器属性已弃用,并且并不总是像您一样想想。

所以,再次引用 MDN 文档 [link]:

已弃用:此功能已从 Web 标准中删除。尽管某些浏览器可能仍然支持它,但它正在被删除。不要在旧项目或新项目中使用它。使用它的页面或 Web 应用程序可能随时中断。

注意:不要依赖此属性来返回正确的浏览器版本。在基于 Gecko 的浏览器(如 Firefox)和基于 WebKit 的浏览器(如 Chrome 和 Safari)中,返回值以“5.0”开头,后跟平台信息。在 Opera 10 及更高版本中,返回的版本也与实际浏览器版本不匹配。

【讨论】:

  • 嗯,真的只测试支持Symbol。我不认为任何引擎都支持所有 ES6 提议的特性。你需要检查你想使用的每个 ES6 特性。
  • 就像一个注释:有一个表格显示 ES6 支持kangax.github.io/compat-table/es6
  • “特征检测”将不起作用,因为您无法尝试/抓住自己的方式来摆脱像胖箭头、letclass 这样的语法错误。它可以检查像"".startsWith 这样的东西,但这总是很容易。要检测基于语法的功能,您需要使用 eval() 来隐藏 ES5 解析器中的无效代码。
  • @user3412159 我编辑了我的答案!请检查新的,因为以前的完全错误!谢谢。
  • 嗯。使用上面的方法是一种hack,或者解决方法,我觉得浏览器应该告诉服务器要提供什么文件,或者提供这样做的选项。所以应该有一个像engineVersion: es5engineVersion: es6这样的请求标头来表明它确实支持它,如果flags变成on/off也应该工作,这样你就不必依赖js @ 987654353@ 标签来加载您的资产。服务器应该能够提供正确的文件以供使用或遗留。但这仍然是一种意见:)
【解决方案2】:

支持 ES6 模块的浏览器供应商现在提供了一种简单的方法来进行特征检测:

...
<head>
  <script nomodule>window.nomodules = true;</script>
  <script>console.log(window.nomodules)</script>
</head>
...

带有nomodule属性的脚本不会被支持&lt;script type="module" ...&gt;的浏览器执行

你也可以像这样注入脚本:

const script = document.createElement('script');
script.setAttribute('nomodule', '');
script.innerHTML = 'window.nomodules = true;';
document.head.insertBefore(script, document.head.firstChild);
script.remove();

【讨论】:

    【解决方案3】:

    正如 Marco Bonelli 所说,检测 ECMAScript 6 语言语法的最佳方法是使用 eval();。如果调用没有抛出错误,则支持“所有其他”功能,但我推荐 Function();.

    function isES6()
    {
        try
        {
            Function("() => {};"); return true;
        }
        catch(exception)
        {
            return false;
        }
    }
    

    演示: https://jsfiddle.net/uma4Loq7/

    【讨论】:

    • 使用eval()Function() 与未指定'unsafe-eval' 的内容安全策略不兼容。
    • 如果您可以访问 index.html,则可以只使用
    • 检测箭头函数支持不会涵盖所有 ES6 特性。 caniuse.com/es6-class,arrow-functions
    【解决方案4】:
    1. 检测 devicePixelRatio 这是WebKit 中的一个特殊属性。
    2. 检测 javaEnabled 函数的实现。

    (function() {
      var v8string = 'function%20javaEnabled%28%29%20%7B%20%5Bnative%20code%5D%20%7D';
      var es6string = 'function%20javaEnabled%28%29%20%7B%0A%20%20%20%20%5Bnative%20code%5D%0A%7D';
    
      if (window.devicePixelRatio) //If WebKit browser
      {
        var s = escape(navigator.javaEnabled.toString());
        if (s === v8string) {
          alert('V099787 detected');
        } else if (s === es6string) {
          alert('ES6 detected')
        } else {
          alert('JSC detected');
        }
      } else {
        display("Not a WebKit browser");
      }
    
      function display(msg) {
        var p = document.createElement('p');
        p.innerHTML = msg;
        document.body.appendChild(p);
      }
    
    })()

    【讨论】:

    • 这个不错,但是我很好奇在firefox上哪里可以直接看到引擎版本?
    • if (window.devicePixelRatio) //If WebKit browser
    【解决方案5】:

    如果您因任何原因无法使用 eval,则可以通过将脚本代码插入其自己的脚本块中来全局检测这一点(并在块中测试变量赋值的值,或使用 window.onerror)。

    <script>
        window.isES6 = false;
    </script>
    <script>
        // Arrow functions support
      () => { };
      
      // Class support
      class __ES6FeatureDetectionTest { };
      
      // Object initializer property and method shorthands
      let a = true;
      let b = { 
        a,
        c() { return true; },
        d: [1,2,3],
       };
      
      // Object destructuring
      let { c, d } = b;
      
      // Spread operator
      let e = [...d, 4];
    
      window.isES6 = true;
    </script>
    
    <script>
    document.body.innerHTML += 'isES6: ' + window.isES6;
    </script>
    

    https://jsfiddle.net/s5tqow91/2/

    请注意,有许多 ES6 功能,仅检查一项并不能保证您已涵盖。 (上面的代码也没有涵盖所有内容,只是我认为是我最常用的功能)。

    还有一个相当流行的 npm 库似乎涵盖了所有这些内容:https://www.npmjs.com/package/feature-detect-es6

    【讨论】:

    • 这应该是公认的答案。使用这种方法,您可以测试对任何 JS 功能的支持,而无需依赖 eval(code)Function(code) 等不安全的黑客攻击。
    【解决方案6】:

    目前还没有一个准确的方法来检测 ES6,但是如果你在当前浏览器中测试它的功能,你可以确定引擎是否是 ES6。我的 esx 库通过语法测试和方法检查来检测 ECMAScript 版本。要知道它可以检测 ECMAScript 3、5、6 和 7(ES7 未测试,但应该可以),如果没有匹配的 ECMAScript 测试,它会给出null 作为结果。

    使用我的库的示例:

    if (esx.detectVersion() >= 6) {
        /* We're in ES6 or above */
    }
    

    【讨论】:

    • 在此存储库中 esdetect.js 的第 46 行使用 eval() 与未指定 'unsafe-eval' 的内容安全策略不兼容。
    【解决方案7】:

    正如 Damian Yerrick 所提到的,使用 eval() 或 Function() 与未指定“unsafe-eval”的内容安全策略不兼容。

    如果浏览器支持 Worker,那么您可以通过在 worker 中实现该语法并检查错误或成功来检测对任何 ES6 语法的支持,例如检测对箭头函数的支持:

    worker.js

    // If ES6 arrow functions are supported then the worker listener will receive true, otherwise it will receive an error message
    (() => {
        postMessage(true);
    })();
    

    index.js

    if (typeof (Worker) !== "undefined") {
    
        var myWorker = new Worker('worker.js');
    
        myWorker.onmessage = function (e) {
            // arrow functions must be supported since we received message from the worker arrow function
        }
    
        myWorker.onerror = function (e) {
            // the worker triggered an error so arrow function not supported (could explicitly check message for syntax error)
        }
    }
    

    【讨论】:

      【解决方案8】:

      将不兼容的语法代码,例如包含箭头函数,放在它自己的脚本块中,并用兼容的语法代码填充它。

      <script>
              // This script block should not compile on incompatible browsers, 
              // leaving the function name undefined.
              // It can then be polyfilled with a function containing compatible syntax code.
              function fame() {
                  /* incompatible syntax code such as arrow functions */
              }
      </script>
      
      <script>
          if (typeof fame !== "function") {
              // alert("polyfill: fame");
              function fame() {
                  /* compatible syntax code */
              }
          }
      </script>
      
      <script>
          // main code
          fame();
      </script>
      

      【讨论】:

        猜你喜欢
        • 2011-09-26
        • 1970-01-01
        • 2011-10-26
        • 1970-01-01
        • 2017-05-20
        • 2013-07-29
        • 1970-01-01
        • 1970-01-01
        • 2016-03-21
        相关资源
        最近更新 更多