Javascript ES6跨浏览器检测


76

如何找到浏览器的Javascript引擎版本以及对ECMAScript 6的支持?

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


你为什么想知道?

没有标准属性,您只需阅读即可获取该信息。您可以构建字典或已知平台或使用功能测试。
dandavis

2
如果要使用新语法,请使用预编译器,该预编译器输出与旧版本兼容的javascript。如果需要新功能,请使用功能检测,例如Marco Bonelli建议。
CodesInChaos

1
您可以使用feature-detect-es6。–
Lloyd

Answers:


122

特征检测

我建议您使用功能检测,而不要使用启发式方法检测浏览器的引擎。为此,您可以简单地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浏览器的规格与其他浏览器不同。

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

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

功能检测+后备

使用功能检测时,一种不确定的工作方式是一种聪明的工作方式,它不确定您可以/不能使用哪些功能,其中包括几种功能检测,以及随之而来的后退到更基本的方法(甚至从零开始创建这些方法),以防出现功能不支持您要使用的。

一个具有后备功能的特征检测的简单示例可能适用于该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(和谐)功能检测

现在,要解决一个真正的问题:如果您想检测对ES6的支持,您将无法像我上面说的那样工作,因为ES6功能的相关范围是基于新的语法和私有词的,并且会抛出一SyntaxError,如果用在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的情况下,才能导入ES6,而不会引发脚本SyntaxErrors

ES6检测和条件执行示例

现在让我们做一个更相关的示例,假设您想在ES6脚本中使用这些功能:

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

注意: 只能使用功能或其他等效功能(例如)来完成对新引入语法(例如箭头功能)的功能检测,因为编写无效语法将在脚本执行之前停止脚本。这也是为什么您不能使用语句来检测类和箭头函数的原因:这些功能与关键字和语法有关,因此包装在块中可以很好地工作。eval()Function()ifeval(...)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文档[ link ]:

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

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


嗯,实际上仅测试对的支持Symbol。我认为尚未有任何引擎支持所有ES6建议的功能。您将需要检查要使用的每个ES6功能。
HBP

2
就像一个注释:有一张表显示了ES6支持kangax.github.io/compat-table/es6
zaynetro 2015年

2
该“功能检测”将不起作用,因为您无法尝试/摆脱诸如粗箭头,letclass之类的语法错误。它可以检查诸如之类的东西 "".startsWith,但这始终很容易。要检测基于语法的功能,您需要使用eval()在ES5解析器中隐藏无效的代码。
dandavis

1
@ user3412159我编辑了答案!请检查新版本,因为上一个完全错误!谢谢。
Marco Bonelli 2015年

1
嗯。使用上面提到的方法是很容易破解的,或者可以解决,我觉得浏览器应该告诉服务器要提供什么文件,或者可以选择这样做。因此,应该有一个诸如此类的请求标头,engineVersion: es5engineVersion: es6表明它确实支持该请求标头,如果flagson/off这种方式转动也应该起作用,则不必依赖jsscript标记来加载资产。服务器应该能够传递正确的文件以使用或保留。但这仍然是一种意见:)
2015年

13

现在,支持ES6模块的浏览器供应商提供了一种简便的功能检测方法:

...
<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();

8

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

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

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


3
未指定的内容安全策略的使用eval()Function()与之不兼容'unsafe-eval'
Damian Yerrick '18

如果您有权访问index.html,则可以仅使用<script>标记来评估表达式。请参阅下面的答案。
Maciej Krawczyk

检测箭头功能支持不会涵盖所有ES6功能。caniuse.com/es6-class,arrow-functions
Maciej Krawczyk

3
  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);
  }

})()


1
很好,但是我很好奇我在哪里可以直接在firefox上看到引擎版本?
RoliCo

5
if (window.devicePixelRatio) //If WebKit browser<=这很糟糕。请参阅Marco Bonelli的答案,以了解为什么您不应该使用浏览器检测,而应该使用功能检测。
Arturo TorresSánchez2015年

1

目前尚没有检测ES6的确切方法,但是如果您在当前浏览器中测试其功能,则可以确定引擎是否为ES6。我的esx库通过执行语法测试和方法检查来检测ECMAScript版本。众所周知,它可以检测ECMAScript 3、5、6和7(未经测试的ES7,但应该可以工作),如果没有匹配的ECMAScript测试,则给出null结果。

使用我的库的示例:

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

2
此存储库eval()中第46行的使用esdetect.js与未指定的内容安全策略不兼容'unsafe-eval'
达米安·耶里克

1

如果由于某种原因无法使用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)
mh8020

0

正如Damian Yerrick所提到的,eval()或Function()的使用与未指定“不安全的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)
    }
}

-1

将不兼容的语法代码(例如包含箭头功能)放入其自己的脚本块中,并用兼容的语法代码对其进行填充。

<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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.