脚本 isMobile.js(移动设备检测库)的核心实现

偶然看到一段代码,原来是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 (移动设备检测库)的核心实现。

整体思路是:

  1. 用一组正则表达式(Regular Expression)把 navigator.userAgent(或用户自己传入的字符串)里能体现「是什么设备」的特征匹配出来。
  2. 把这些特征组装成一个对象,返回给调用者。调用者只要读取 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 字符串
  • 返回 :true if 正则匹配成功,false otherwise。

这个函数把「正则 → 测试 → 布尔」这一步封装起来,后面的检测代码可以直接写成 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 = {...}, ...)

这里的 步骤:

  1. 取 UA :如果调用者显式传入 a(如 new r("...")),就使用它;否则使用 navigator.userAgent。
  2. 去除 Facebook / Twitter 包装 :
    • r.split("[FBAN") 把 UA 按 "[FBAN" 切开,通常 UA 里会出现 [FBAN...] 的部分(Facebook App 注入的),后面的 [0] 就是原始 UA。
    • 同理处理 Twitter。这波清洗是为了让后面的正则更加可靠。
  3. 构造检测结果对象 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 等各种场景都能快速判断「是不是手机」或「是不是平板」。


可能的局限性(顺便提及)

  1. UA 可被伪造 :恶意脚本可以随意修改 userAgent,导致误判。
  2. 新设备 выпуска:如果出现新的型号(如新 iPhone、新 Android 机型)不在正则里,检测会失效,需要手动更新正则。
  3. Windows 10 Mobile 已停产 :相关正则(如 Windows Phone)在今天已基本不再产生流量。

希望这段拆解能帮助你彻底弄清楚代码的意图和实现细节!如果还有其它细节想了解(比如如何扩展正则、或者怎样在项目中更好维护),随时告诉我。祝你开发顺利 🚀.

相关推荐
樱花落木兰14 分钟前
分布式登录实战:Session 会话共享改造,Redis 存储用户登录状态
java·javascript·数据库·redis·分布式·缓存
wuyk55517 分钟前
Python 网络爬虫入门到实战 第 04 章:请求头、UA 伪装、超时、异常处理、基础反爬绕过
开发语言·爬虫·python
流形填表22 分钟前
题库去重实战:基于题干指纹的重复题清理
开发语言·c#
\光辉岁月/24 分钟前
3.java运算符
java·开发语言
知识分享小能手31 分钟前
C++ 学习教程,从入门到精通,string类和标准模板库 — 完整知识点详解(16)
开发语言·c++·学习
qq_4017004136 分钟前
Qt 炫酷曲线,图表,2D/3D开源库
开发语言·qt
liangshanbo121543 分钟前
面试题:线上 JavaScript 报错如何快速定位到源码?
开发语言·javascript·ecmascript
weixin_440730501 小时前
迭代器Iterator(列表生成器、可迭代的如何生成迭代器)
开发语言·python
朝朝辞暮i1 小时前
C++ 第 31C 课:Subscriber 到底是什么
开发语言·c++·算法
weixin_440730501 小时前
内置函数、json文本、pickle二进制
开发语言·python·json