特征检测
我建议您使用特征检测,而不是使用启发式方法检测浏览器的引擎。为此,您可以简单地在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 :(
}
为什么特征检测优于浏览器/引擎检测?
在大多数情况下,有多种原因使特征检测成为最佳选择:
这些是恕我直言使特征检测成为最佳方法的主要原因。
特征检测+回退
使用特征检测时,当您不确定可以/不能使用哪些功能时,一种非常聪明的工作方式包括几个特征检测和后续回退到更基本的方法(甚至从头开始创建这些方法),以防您想要使用的功能不受支持。
带有回退的特征检测的简单示例可以应用于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 及更高版本中,返回的版本也与实际浏览器版本不匹配。