JavaScript ES6跨浏览器检测

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

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

42042 次浏览
  1. 检测设备像素比率是 WebKit中的一个特殊属性。
  2. 检测 javaEnable 函数的实现。

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


})()

特征提取

我建议您使用 特征提取,而不是用启发式方法检测浏览器的引擎。要做到这一点,你可以简单地 在 ABC0语句中包装一些代码,或者使用一些 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的情况下才导入 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>
    
  • Code in your es5script.js script:

    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 :(
    }
    
  • Code in your 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!
    

Browser/engine detection

Like I said above, browser and engine detection are not the best practices when programming some JavaScript script. I'm gonna give you some background on this topic, just not to leave my words as a "random personal opinion".

Quoting from the MDN Documentation [link]:

When considering using the user agent string to detect which browser is being used, your first step is to try to avoid it if possible. Start by trying to identify why you want to do it.

[...] Are you trying to check for the existence of a specific feature? Your site needs to use a specific Web feature that some browsers don't yet support, and you want to send those users to an older Web site with fewer features but that you know will work. This is the worst reason to use user agent detection, because odds are eventually all the other browsers will catch up. You should do your best to avoid using user agent sniffing in this scenario, and do feature detection instead.

Also, you're saying you use navigator.appVersion, but consider using another approach, because that one, together with many other navigator properties, is deprecated, and doesn't always behave like you think.

So, quoting from the MDN Documentation [link] again:

Deprecated: this feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time.

Note: Do not rely on this property to return the correct browser version. In Gecko-based browsers (like Firefox) and WebKit-based browsers (like Chrome and Safari) the returned value starts with "5.0" followed by platform information. In Opera 10 and newer the returned version does not match the actual browser version, either.

目前还没有检测 ES6的确切方法,但是如果在当前浏览器中测试其特性,就可以确定引擎是否为 ES6。我的 埃塞克斯库通过语法测试和方法检查来检测 ECMAScript 版本。为了知道它可以检测 ECMAScript 3,5,6和7(ES7未测试,但应该工作) ,如果没有 ECMAScript 测试匹配,它给 null作为结果。

使用我的库的例子:

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

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

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

正如 Marco Bonelli 所说,检测 ECMAScript 6语言语法的最佳方法是使用 Eval () ;。如果调用不抛出错误,则支持“所有其他”特性,但我建议使用 功能() ;

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

Demo: < a href = “ https://jsfiddle.net/uma4Loq7/”rel = “ norefrer”> https://jsfiddle.net/uma4loq7/

支持 ES6模块的浏览器供应商现在提供了一种简单的特征提取:

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

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

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

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

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

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

工人 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)
}
}

没有 ES6特征提取

你可以这样做 不用 eval-只需在它自己的脚本块中插入检测代码,并在结尾处进行全局变量分配。如果脚本块中出现任何错误,变量赋值将不会运行。

<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有许多特性,只选择一个并不能保证您被覆盖。(上面的代码也没有涵盖所有内容,它只是我认为是我最常用的特性)。

为什么不做评估?

主要的原因是安全性,并不是说调用 eval 特征提取本身就是不安全的。在理想情况下,您应该禁止使用 Content Security Policy 进行 eval,这样它就根本不能被使用了——这大大降低了攻击面。但是如果您自己的代码使用 eval,则不能这样做。

这个函数在 Chrome98.0.4758.80和 Firefox97.0.2中返回 true (刚刚测试过)。它可能不适用于其他浏览器和以前版本的 Chrome/Firefox (错误的负面结果)

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

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

当然,这就回避了一个问题,即如何从 ES6之后的版本中检测新的语法,比如“ ? ?”然后呢?操作员。