偶然看到一段代码,原来是isMobile.js(移动设备检测库)的核心实现
javascript
!function (a) { var b = /iPhone/i, c = /iPod/i, d = /iPad/i, e = /(?=.*\bAndroid\b)(?=.*\bMobile\b)/i, f = /Android/i, g = /(?=.*\bAndroid\b)(?=.*\bSD4930UR\b)/i, h = /(?=.*\bAndroid\b)(?=.*\b(?:KFOT|KFTT|KFJWI|KFJWA|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|KFARWI|KFASWI|KFSAWI|KFSAWA)\b)/i, i = /Windows Phone/i, j = /(?=.*\bWindows\b)(?=.*\bARM\b)/i, k = /BlackBerry/i, l = /BB10/i, m = /Opera Mini/i, n = /(CriOS|Chrome)(?=.*\bMobile\b)/i, o = /(?=.*\bFirefox\b)(?=.*\bMobile\b)/i, p = new RegExp("(?:Nexus 7|BNTV250|Kindle Fire|Silk|GT-P1000)", "i"), q = function (a, b) { return a.test(b); }, r = function (a) { var r = a || navigator.userAgent, s = r.split("[FBAN"); if ("undefined" != typeof s[1] && (r = s[0]), s = r.split("Twitter"), "undefined" != typeof s[1] && (r = s[0]), this.apple = { phone: q(b, r), ipod: q(c, r), tablet: !q(b, r) && q(d, r), device: q(b, r) || q(c, r) || q(d, r) }, this.amazon = { phone: q(g, r), tablet: !q(g, r) && q(h, r), device: q(g, r) || q(h, r) }, this.android = { phone: q(g, r) || q(e, r), tablet: !q(g, r) && !q(e, r) && (q(h, r) || q(f, r)), device: q(g, r) || q(h, r) || q(e, r) || q(f, r) }, this.windows = { phone: q(i, r), tablet: q(j, r), device: q(i, r) || q(j, r) }, this.other = { blackberry: q(k, r), blackberry10: q(l, r), opera: q(m, r), firefox: q(o, r), chrome: q(n, r), device: q(k, r) || q(l, r) || q(m, r) || q(o, r) || q(n, r) }, this.seven_inch = q(p, r), this.any = this.apple.device || this.android.device || this.windows.device || this.other.device || this.seven_inch, this.phone = this.apple.phone || this.android.phone || this.windows.phone, this.tablet = this.apple.tablet || this.android.tablet || this.windows.tablet, "undefined" == typeof window) return this; }, s = function () { var a = new r; return a.Class = r, a; }; "undefined" != typeof module && module.exports && "undefined" == typeof window ? module.exports = r : "undefined" != typeof module && module.exports && "undefined" != typeof window ? module.exports = s() : "function" == typeof define && define.amd ? define("isMobile", [], a.isMobile = s()) : a.isMobile = s(); }(this);
MiniMax的说明
这段代码的作用
这段脚本是 isMobile.js (移动设备检测库)的核心实现。
整体思路是:
- 用一组正则表达式(Regular Expression)把
navigator.userAgent(或用户自己传入的字符串)里能体现「是什么设备」的特征匹配出来。 - 把这些特征组装成一个对象,返回给调用者。调用者只要读取
isMobile.phone、isMobile.tablet、isMobile.any等属性,就能知道当前页面是否运行在手机、平板或其它移动设备上。
下面逐段、逐行解释代码的细节。
1. 外层 IIFE(立即执行函数表达式)
!function (a) {
// ......
}(this);
!function (a){ ... }等价于(function(a){ ... }),是一种常见的"把代码包装成模块"写法。- 传入的实参
this在浏览器环境下是window,在 Node 环境里是global(尽管后面会判断window是否存在)。 - 这样可以把所有变量 封闭 在函数内部,防止污染全局命名空间,只把最终需要的
isMobile暴露出去。
2. 正则表达式(device signatures)
下面是一组用来匹配 User‑Agent (UA)字符串的正则,全部使用 i(不区分大小写)标志。
| 变量 | 正则 | 用途 |
|---|---|---|
b |
/iPhone/i |
iPhone |
c |
/iPod/i |
iPod |
d |
/iPad/i |
iPad |
e |
/(?=.*\bAndroid\b)(?=.*\bMobile\b)/i |
Android 手机(同时出现 Android 与 Mobile) |
f |
/Android/i |
任意 Android 设备(包括平板) |
g |
/(?=.*\bAndroid\b)(?=.*\b(?:SD4930UR\b))/i |
Amazon 手机(Kindle Fire 的手机版) |
h |
`/(?=.\bAndroid\b)(?=.\b(?:KFOT | KFTT |
i |
/Windows Phone/i |
Windows Phone |
j |
/(?=.*\bWindows\b)(?=.*\bARM\b)/i |
Windows on ARM(例:Surface RT) |
k |
/BlackBerry/i |
BlackBerry 老手机 |
l |
/BB10/i |
BlackBerry 10 |
m |
/Opera Mini/i |
Opera Mini 浏览器 |
n |
`/(CriOS | Chrome)(?=.*\bMobile\b)/i` |
o |
/(?=.*\bFirefox\b)(?=.*\bMobile\b)/i |
Firefox 移动版 |
p |
`new RegExp("(?:Nexus 7 | BNTV250 |
说明
(?= ... )是先行断言 (look‑ahead),用来要求同时满足多个条件但不消耗字符。\b表示单词边界,防止把 "Android" 匹配到 "AndroidX" 之类。i标志让匹配不区分大小写,因为 UA 字符串往往大小写混杂。
3. 辅助函数 q
var q = function (a, b) {
return a.test(b);
};
a:一个正则(RegExp实例)b:待检测的 UA 字符串- 返回 :
trueif 正则匹配成功,falseotherwise。
这个函数把「正则 → 测试 → 布尔」这一步封装起来,后面的检测代码可以直接写成 q(b, r) 而不必每次写 b.test(r)。
4. 核心构造函数 r
var r = function (a) {
var r = a || navigator.userAgent,
s = r.split("[FBAN");
if ("undefined" != typeof s[1] && (r = s[0]), s = r.split("Twitter"),
"undefined" != typeof s[1] && (r = s[0]), this.apple = {...}, ...)
这里的 步骤:
- 取 UA :如果调用者显式传入
a(如new r("...")),就使用它;否则使用navigator.userAgent。 - 去除 Facebook / Twitter 包装 :
r.split("[FBAN")把 UA 按 "[FBAN" 切开,通常 UA 里会出现[FBAN...]的部分(Facebook App 注入的),后面的[0]就是原始 UA。- 同理处理
Twitter。这波清洗是为了让后面的正则更加可靠。
- 构造检测结果对象
this(即new r()的实例),属性如下:
| 类别 | 属性 | 检测逻辑 |
|---|---|---|
| apple | phone |
匹配 iPhone(b) |
ipod |
匹配 iPod(c) |
|
tablet |
不是 iPhone 且 匹配 iPad(!q(b,r) && q(d,r)) |
|
device |
是 phone、ipod 或tablet 任一 | |
| amazon | phone |
匹配 Amazon 手机(g) |
tablet |
不是 phone 且 匹配 Amazon 平板(!q(g,r) && q(h,r)) |
|
device |
是 phone 或 tablet | |
| android | phone |
Amazon phone (g) 或 通用 Android 手机 (e) |
tablet |
不是 phone 且 匹配 Amazon 平板 (h) 或 通用 Android (f) |
|
device |
phone、tablet、g、h、e、f 任一 |
|
| windows | phone |
匹配 Windows Phone(i) |
tablet |
匹配 Windows ARM(j) |
|
device |
phone 或 tablet | |
| other | blackberry |
BlackBerry 老机 (k) |
blackberry10 |
BB10 (l) |
|
opera |
Opera Mini (m) |
|
firefox |
Firefox 移动版 (o) |
|
chrome |
Chrome 移动版 (n) |
|
device |
任意上面的 true |
|
| seven_inch | - | 匹配 p(Nexus 7、Kindle Fire 等) |
| any | - | `apple.device |
| phone | - | `apple.phone |
| tablet | - | `apple.tablet |
在
android判定里,g(Amazon phone)与e(Android Mobile)都会标记为 phone ,而h(Amazon 平板) 与f(普通 Android)则归为 tablet。这样即使在同一个"Android"体系里也能把平板与手机区分出来。
5. 输出方式(模块化)
var s = function () {
var a = new r;
a.Class = r;
return a;
};
s()创建一个 实例 (把检测结果放进对象里),并把原始构造函数r挂在这个实例的Class属性上,方便后续继承或二次包装。
5.1 Node.js(CommonJS)
"undefined" != typeof module && module.exports && "undefined" == typeof window
? module.exports = r
- 当
module.exports存在且没有window(Node 环境),直接把构造函数r导出。这样使用方可以var isMobile = require('isMobile');并new isMobile()。
5.2 浏览器全局 + AMD
"undefined" != typeof module && module.exports && "undefined" != typeof window
? module.exports = s()
: "function" == typeof define && define.amd
? define("isMobile", [], a.isMobile = s())
: a.isMobile = s();
- AMD :如果检测到
define(RequireJS),则用define定义为模块isMobile。 - 普通浏览器 :直接把
s()(即new r()的实例)赋值给全局对象a.isMobile(a正是传入的this,通常等价于window)。
这里的四处判断确保了 同构 :无论在 Node、浏览器还是 RequireJS 环境,都只需要一次引用
isMobile就能得到统一的检测对象。
6. 使用示例
// 假设在浏览器中执行,isMobile 已经挂在 window 上
if (isMobile.phone) {
alert("你在手机上");
}
if (isMobile.tablet) {
alert("你在平板上");
}
if (isMobile.android.device) {
console.log("设备是 Android 家族");
}
if (isMobile.any) {
// 任意移动设备(包括 7 寸平板)都会进来
console.log("移动设备访问");
}
Node/ RequireJS 用法同理:
var isMobile = require('isMobile');
isMobile.phone // => true/false
isMobile.tablet
isMobile.any
7. 小结
- 正则 把 UA 字符串拆分成若干「特征」;
- 构造函数
r把这些特征聚合成布尔属性(phone、tablet、device等); - 外层包装 根据当前运行平台(Node / 浏览器 / AMD)选择合适的导出方式,使得库在所有主流环境都能即插即用。
这段代码的核心价值在于:只依赖 UA ,不需要浏览器特性检测(
window.orientation、touch事件等),从而在 服务端渲染 、静态页面 、WebView 等各种场景都能快速判断「是不是手机」或「是不是平板」。
可能的局限性(顺便提及)
- UA 可被伪造 :恶意脚本可以随意修改
userAgent,导致误判。 - 新设备 выпуска:如果出现新的型号(如新 iPhone、新 Android 机型)不在正则里,检测会失效,需要手动更新正则。
- Windows 10 Mobile 已停产 :相关正则(如
Windows Phone)在今天已基本不再产生流量。
希望这段拆解能帮助你彻底弄清楚代码的意图和实现细节!如果还有其它细节想了解(比如如何扩展正则、或者怎样在项目中更好维护),随时告诉我。祝你开发顺利 🚀.