Javascript ES6 跨浏览器检测

IT技术 javascript ecmascript-6
2021-01-28 06:52:36

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

navigator.appVersion只是想知道浏览器的版本,而不是引擎的版本。

6个回答

特征检测

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

例如:

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 的相关特性范围是基于新的语法和私有词,并且会抛出a SyntaxErrorif 在 ES5 中使用,这意味着编写包含 ES5 和 ES6 的脚本是不可能的!

这是一个演示此问题的示例;下面的代码段不起作用,它会在执行前被阻止,因为包含非法语法。

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语句来检测类和箭头函数的原因:这些特性是关于关键字和语法的,所以eval(...)包裹在try {...} catch (e) {...}块中会很好用。

所以,来到真正的代码:

  • 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 文档 [链接]:

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

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

此外,您是说您使用navigator.appVersion,但请考虑使用另一种方法,因为该方法与许多其他导航器属性一起弃用,并且其行为并不总是像您想象的那样。

因此,再次引用 MDN 文档 [链接]:

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

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

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

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

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

具有该nomodule属性的脚本不会被支持的浏览器执行<script type="module" ...>

您还可以像这样注入脚本:

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

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

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

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

检测箭头函数支持不会涵盖所有 ES6 特性。caniuse.com/es6-class,箭头函数
2021-03-16 06:52:36
如果您有权访问 index.html,则可以仅使用 <script> 标记来评估表达式。请看我下面的回答。
2021-03-18 06:52:36
未指定的内容安全策略的使用eval()Function()不兼容'unsafe-eval'
2021-04-01 06:52:36
  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);
  }

})()

if (window.devicePixelRatio) //If WebKit browser<= 这很糟糕。请参阅 Marco Bonelli 的回答,了解为什么不应该使用浏览器检测,而是使用特征检测。
2021-03-23 06:52:36
这很好,但我很好奇我在哪里可以直接看到 firefox 上的引擎版本?
2021-03-26 06:52:36

如果您因任何原因不能使用 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)
2021-03-26 06:52:36