夜雨聆风学习资料网

ARTICLE · 1061235

JavaScript如何识别你的浏览器和电脑?客户端检测一次讲透

JavaScript如何识别你的浏览器和电脑?客户端检测一次讲透
点击蓝字 关注我们

前几天,我接到一个需求,产品经理跑过来说:“咱们的网站能不能根据用户电脑的情况,自动调整一下页面?”

我问:“什么情况?”

他说:“比如用户用 Chrome,就给他推荐最新版功能;如果是 Safari,就提示部分功能;如果用户电脑屏幕比较小,页面布局紧凑一点;如果设备性能比较弱,就少加载一点动画。”

听起来是不是挺合理?但问题来了:JavaScript 怎么知道用户到底是什么浏览器、什么操作系统、什么设备?这就涉及前端开发中一个很经典的话题——客户端检测

简单来说,客户端检测就是让 JavaScript 像一个“门卫”,用户刚进门的时候,偷偷观察一下他的身份信息。

  • “你用的是什么浏览器?”

  • “什么操作系统?”

  • “屏幕多大?”

  • “CPU 有多少个逻辑核心?”

  • “设备内存大概多少?”

  • “支持哪些浏览器能力?”

这些信息综合起来,就构成了我们今天要聊的:JavaScript 客户端检测中的软件与硬件检测。

PART01
识别浏览器与操作系统

先想象一个场景,公司前台来了一个人,我们想知道他是谁,最直接的办法是什么?看他的身份证。

在浏览器里,这张“身份证”有一个非常经典的东西:User-Agent,也就是用户代理字符串。

JavaScript 可以通过 navigator.userAgent 获取它,例如:console.log(navigator.userAgent);

你可能看到类似:

从这里可以观察到一些信息:

  • Macintosh:可能表示 macOS

  • Intel Mac OS X:操作系统相关信息

  • Chrome/150:Chrome 浏览器

  • AppleWebKit:浏览器内核相关信息

  • Safari:兼容性标识

传统客户端检测,就是通过这些字符串判断浏览器和操作系统,比如:

看起来非常简单,但这里其实埋了一个坑:User-Agent 并不是一份绝对可靠的身份证,因为浏览器可以伪装自己的 User-Agent,甚至不同浏览器之间也可能故意保留一些历史字符串,例如 Chrome 的 User-Agent 里经常能看到 Safari,这并不意味着用户正在使用 Safari。

所以,如果你看到 ua.includes("Safari"),然后直接认为用户一定使用 Safari,那就容易翻车。

更合理的思路是:识别浏览器时,尽量使用结构化的浏览器信息;如果不得不分析 User-Agent,则应该结合多个条件,而不是只看一个关键词。

PART02
浏览器元数据:navigator 就像浏览器的档案袋

如果说 User-Agent 是身份证,那么 navigator 对象就更像一个人的“档案袋”,里面装着浏览器愿意告诉 JavaScript 的各种信息,例如:

这些属性分别可以帮助我们了解不同的信息。

这里有一个非常重要的思想:客户端检测并不是为了“偷窥用户”,而是为了让网页更适应当前环境。

比如一个网页需要展示中文、英文、日文,我们就可以读取 console.log(navigator.language);

  • 如果用户首选语言是:zh-CN,页面可以默认展示中文。

  • 如果是:en-US,就可以默认展示英文。

当然,真正成熟的网站不会只相信浏览器提供的信息,还会结合用户手动选择、HTTP 请求头、服务端配置等共同决定语言。

PART03
别急着判断浏览器,先判断“能力”

说到这里,特别想强调一个前端开发中的经典思想:能力检测通常比浏览器检测更加可靠。

什么意思?假设我们想使用某个浏览器 API,以前很多代码会这样写:

这相当于“你是不是 Chrome?如果是,我猜你应该支持这个功能。”

这其实是“猜”,而能力检测是“我不管你是谁,我直接问你有没有这个能力。”,例如:

这就更加合理,因为我们真正关心的并不是“你到底是不是 Chrome?”,而是“你到底能不能完成这件事?”,这也是现代 Web 开发非常重要的思想:

  • 浏览器名称只是“身份”。

  • 浏览器能力才是“实力”。

PART04
硬件检测:开始调查用户的“装备”

软件环境看完之后,我们再看看硬件,这时候 JavaScript 就像一个维修师傅,开始检查电脑配置,首先是 CPU,浏览器可以通过 navigator.hardwareConcurrency 获取一个表示逻辑处理器数量的值。

例如:console.log(navigator.hardwareConcurrency); 如果得到 8,可以理解为当前浏览器环境报告有大约 8 个逻辑处理器,这个数据有什么用?

假设我们的网站有一个复杂的数据处理任务,我们可以根据设备情况决定是否启用更多并行任务,例如:

但这里也不能简单理解成 CPU 核心越多,电脑一定越快,因为真实性能还受到 CPU 架构、频率、负载、浏览器限制、功耗模式等因素影响。

所以 hardwareConcurrency 更适合作为性能策略的参考指标,而不是性能跑分。

PART05
deviceMemory:看看设备大概有多少内存

另外一个比较有意思的属性是 navigator.deviceMemory,它可以提供设备内存的大致信息。

例如:console.log(navigator.deviceMemory);,可能得到 8,通常可以理解为设备大约有 8GB 内存。

注意关键词:大约,浏览器出于隐私和安全方面的考虑,并不会把所有硬件信息原封不动地交给网页。

所以我们不能把这个数字理解成“用户电脑物理内存精确就是 8GB。”,更适合的使用方式是:

比如一个网页里有大量高清图片、复杂动画、WebGL 场景,如果设备内存条件比较有限,就可以考虑:

  • 降低图片分辨率

  • 延迟加载资源

  • 减少动画

  • 降低 WebGL 质量

  • 减少同时运行的任务

这就叫:渐进增强 + 性能降级。

PART06
触摸屏:判断用户是不是“用手指点网页”

还有一个很有意思的信息:

navigator.maxTouchPoints

例如:

这在响应式设计、交互设计中非常有用,比如:

  • 桌面网站喜欢鼠标悬停显示菜单。

  • 但是手机用户根本没有鼠标悬停这个概念。

所以我们可以根据输入能力调整交互方式。

不过同样需要注意触摸能力不等于手机,现在很多 Windows 笔记本、平板电脑、二合一设备同样支持触摸,所以支持触摸 ≠ 手机,这是客户端检测中非常容易犯的错误。

PART07
屏幕检测:硬件和软件之间的“交界地带”

说到设备,我们还经常需要知道屏幕情况,这时候就轮到 screen 对象登场了,例如:

它可以帮助我们了解屏幕尺寸以及可用空间,但这里又有一个经典概念:屏幕尺寸不等于浏览器视口尺寸。

例如 window.innerWidth 描述的是浏览器当前视口的宽度。,而 screen.width 描述的是屏幕相关尺寸,一个用户可能拥有 2560 像素宽的显示器,但浏览器窗口只占了其中一半。

所以网页布局真正关心的时候,往往应该优先考虑 window.innerWidth,而不是简单根据 screen.width 决定页面布局。

PART08
客户端检测最容易犯的错误

聊到这里,我们可以总结一下,很多刚接触客户端检测的开发者,特别喜欢写:

然后把整个网站逻辑建立在浏览器名称上,这种方式最大的问题就是浏览器会变化,能力才是真正稳定的东西。

比如我们真正想做的是支持某个 API 才执行某段代码,那么就应该检测:

而不是:

这就像招聘程序员。

  • 你是问:“你是不是 Java 程序员?”

  • 还是直接问:“你会不会 Java?”

显然后者更加直接。

PART09
把软件检测和硬件检测组合起来

真正的项目里,我们可以把这些信息组合起来,例如,一个网页应用启动的时候,可以形成一个简单的客户端环境画像:

然后根据这些信息制定策略,例如:

但要记住:这些数据都应该被当成“提示信息”,而不是绝对真相,客户端环境信息可能缺失、可能被限制、可能被伪装。

因此,涉及安全的事情,例如权限判断、身份认证、支付校验,绝不能依赖 JavaScript 客户端检测。

PART010
最后:客户端检测真正检测的是什么?

写到这里,我觉得客户端检测其实挺有意思,表面上,我们是在检测:

  • 浏览器是什么?

  • 操作系统是什么?

  • CPU 有多少核心?

  • 内存有多少?

  • 屏幕有多大?

但更深一层,其实是在回答一个问题:“当前用户所处的运行环境,适合采用什么策略?”

  • 浏览器检测解决的是“你是谁”。

  • 能力检测解决的是“你会什么”。

  • 硬件检测解决的是“你的装备怎么样”。

  • 屏幕和输入设备检测解决的是“你准备怎么操作”。

最终,现代前端真正追求的并不是把用户的电脑查得一清二楚,而是根据有限的环境信息,给用户提供尽可能合适的体验。

所以,如果以后面试官问你:“JavaScript 客户端检测包括哪些内容?”

你可以从三个方向展开:

  • 软件环境——浏览器、操作系统、浏览器元数据;

  • 能力环境——浏览器到底支持什么;

  • 硬件环境——CPU、内存、触摸设备、屏幕等。

而在真正的工程实践中,优先级通常应该是:能力检测 > 浏览器名称判断。

毕竟,我们写前端代码的最终目的,从来不是知道用户“是谁”,而是知道“我应该怎样更好地服务他。”

END

我是小米,一个喜欢分享技术的31岁程序员大哥哥。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!

相关学习资料