繁体   English   中英

Javascript ES6跨浏览器检测

[英]Javascript ES6 cross-browser detection

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

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

特征检测

我建议您使用特征检测而不是使用启发式方法检测浏览器的引擎。 为此,您可以简单地将一些代码包装在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 的相关特性范围是基于新的语法和私有词,并且会抛出如果在 ES5 中使用,则出现SyntaxError ,这意味着编写包含 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关键字构建的class
  • 箭头 ( (...)=>{...} ) 函数

注意:新引入的语法(如箭头函数)的特征检测只能使用eval()函数或其他等效Function()例如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 模块的浏览器供应商现在提供了一种简单的方法来进行特征检测:

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

支持<script type="module" ...>浏览器不会执行带有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/

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

如果由于任何原因无法使用 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

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

使用我的库的示例:

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

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

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

工人.js

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

索引.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)
    }
}

此 function 在 Chrome 98.0.4758.80 和 Firefox 97.0.2(刚刚测试)中返回 true。 它可能不适用于其他浏览器和以前版本的 Chrome/Firefox(假阴性结果)

function hasAsyncSupport () {
    return Object.getPrototypeOf(async function() {}).constructor.toString().includes('Async')
}

我认为最好的方法是将所有 ES6 脚本简单地编写为模块,并使用 nomodule 脚本标签进行任何后备。 不需要尝试编写内联检测代码。

当然,这引出了如何从后 ES6 版本中检测新语法的问题,例如“??” 和 ”?。” 运营商。

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

<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>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM