第十章:浏览器对象模型 (BOM) 与现代 Web API 摘要 : 在第九章,我们征服了文档本身(DOM)。现在,我们将把视野扩大到承载文档的整个 浏览器环境 。本章将深入探讨 浏览器对象模型 (BOM) ,这是一套让我们能够与浏览器窗口、导航历史、用户屏幕乃至客户端存储进行交互的强大 API。我们将从一个个真实且重要的开发场景出发,学习如何控制页面跳转、让网站拥有“记忆”、感知用户环境,并最终掌握一系列让网页功能比肩原生应用的 2025+ 现代 Web API。
在本章中,我们将围绕“赋予网页更多能力”这一核心,探索以下主题:
首先,我们将理解 window
对象 作为全局上下文的“上帝视角”。 接着,我们将学习如何通过 页面导航与历史管理 ,构建现代单页应用(SPA)的基石。 然后,我们将深入 客户端数据持久化 方案,让网页拥有“记忆”用户的能力。 紧接着,我们将学习如何进行 环境感知与特性检测 ,编写出更健壮的跨平台代码。 最后,我们将探索一系列 现代 Web API ,为我们的网页赋予调用系统分享、安全读写剪贴板等原生能力。 10.1. window
对象:作为全局上下文的“上帝视角” 核心内容 : 在我们与浏览器进行任何交互之前,必须先认识 JavaScript 在浏览器环境中的“根”对象——window
。它身兼二职:既是 BOM 的核心,代表着整个浏览器窗口;又是所有 JavaScript 代码执行的 全局作用域 (Global Scope) 。
理解 window
作为全局作用域,意味着在脚本顶层声明的 var
变量和函数,都会自动成为 window
对象的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script > var globalVar = "我是一个全局变量" ; function globalFunc ( ) { console .log ("我是一个全局函数" ); } console .log ('通过 window 访问变量:' , window .globalVar ); window .globalFunc (); let blockScopedVar = "我不会出现在 window 上" ; console .log ('window 上有 blockScopedVar 吗?' , window .blockScopedVar ); </script >
1 2 3 通过 window 访问变量: 我是一个全局变量 我是一个全局函数 window 上有 blockScopedVar 吗? undefined
window
对象还提供了一些基础的、用于与用户进行简单模态交互的方法。
方法 描述 返回值 alert(message)
弹出一个带有一段信息和一个“确定”按钮的警告框。 undefined
confirm(message)
弹出一个带有信息、一个“确定”和一个“取消”按钮的对话框。 true
(用户点击确定) 或 false
(用户点击取消)prompt(message, [default])
弹出一个带有一段信息和一个文本输入框的对话框。 用户输入的字符串,或 null
(用户点击取消)
示例:一个简单的用户确认流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <button id ="delete-btn" > 删除重要文件</button > <p id ="status-msg" > </p > <script > const deleteBtn = document .getElementById ('delete-btn' ); const statusMsg = document .getElementById ('status-msg' ); deleteBtn.addEventListener ('click' , () => { const isConfirmed = window .confirm ('你确定要删除这个重要文件吗?此操作不可逆!' ); if (isConfirmed) { const password = window .prompt ('请输入你的管理员密码以确认删除:' ); if (password === '123456' ) { statusMsg.textContent = '文件已成功删除。' ; window .alert ('操作成功!' ); } else if (password !== null ) { statusMsg.textContent = '密码错误,删除操作已取消。' ; } else { statusMsg.textContent = '用户取消了操作。' ; } } else { statusMsg.textContent = '删除操作已取消。' ; } }); </script >
alert
, confirm
, prompt
都会 阻塞 JavaScript 的执行和页面的渲染,直到用户与之交互。它们在现代 Web 开发中应谨慎使用,通常只用于简单的调试或需要强行中断用户流程的场景。更复杂的交互应使用自定义的 HTML/CSS 模态框组件。
10.2. 页面导航与历史管理:构建单页应用 (SPA) 的基石 核心场景 : 在传统网站中,每次点击链接都会导致整个页面的白屏、刷新和重新加载,这种体验稍显缓慢。而现代 Web 应用(如 GitHub, Gmail)感觉更像桌面程序,它们可以在不刷新整个页面的情况下,流畅地切换内容并同步更新浏览器地址栏中的 URL。
这种流畅的体验是如何实现的?答案就藏在 location
和 history
这两个 BOM 对象中。它们是所有现代前端框架(如 Vue Router, React Router)实现“客户端路由”的底层基石。
10.2.1. location
对象:URL 的编程接口 window.location
对象提供了对当前页面 URL 的详细信息的访问,并允许我们通过代码来触发页面跳转。
读取 URL 信息 location
对象将一个完整的 URL 精确地分解为多个部分,方便我们读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <p > 当前页面的 URL 信息:</p > <pre id ="location-output" > </pre > <script > const output = document .getElementById ('location-output' ); const info = ` href: ${location.href} (完整 URL) protocol: ${location.protocol} (协议) hostname: ${location.hostname} (主机名) port: ${location.port} (端口) host: ${location.host} (主机名 + 端口) pathname: ${location.pathname} (路径) search: ${location.search} (查询字符串) hash: ${location.hash} (锚点) ` ; output.textContent = info.trim (); </script >
sandbox
环境下的 URL 可能比较特殊,但这些属性的分解方式在真实 URL 中是完全一致的。
修改 URL 以实现跳转 方法/属性 描述 对历史记录的影响 location.href = '...'
(常用) 将页面导航到新的 URL。在历史记录中 新增 一条记录。 location.assign('...')
功能与设置 href
完全相同。 在历史记录中 新增 一条记录。 location.replace('...')
用新的 URL 替换 当前页面。 不 在历史记录中新增记录,用户无法通过“后退”按钮返回。location.reload()
重新加载当前页面。 -
10.2.2. history
对象:与浏览器会话历史交互 window.history
对象允许我们与浏览器的会话历史进行交互。
基础导航 这三个方法模拟了用户点击浏览器“前进”、“后退”按钮的行为。
history.back()
: 后退一步。history.forward()
: 前进一步。history.go(delta)
: 移动到指定位置。history.go(-1)
等同于 back()
,history.go(1)
等同于 forward()
。SPA 路由核心:pushState
与 replaceState
这正是实现 SPA “无刷新”导航的魔法所在。这两个方法可以在 不触发页面刷新的前提下,动态地修改浏览器地址栏的 URL ,并管理会话历史。
history.pushState(state, title, url)
: 向会话历史栈中 推入 一个新的状态。history.replaceState(state, title, url)
: 替换 当前的历史状态。参数 :
state
: 一个与新历史记录相关联的 JavaScript 对象。当用户通过前进/后退导航到这个状态时,popstate
事件会携带这个对象。title
: 一个简短的标题,目前大部分浏览器会忽略此参数。url
: 新的历史记录的 URL。必须与当前页面同源。popstate
事件 : 当用户点击浏览器的前进/后退按钮,或者通过代码调用 history.back()
等方法导致活动历史记录发生变化时,window
会触发 popstate
事件。
综合示例:从零实现一个最简 SPA 路由器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <style > nav a { margin : 0 10px ; cursor : pointer; color : blue; text-decoration : underline; } #app-root { margin-top : 20px ; padding : 15px ; border : 1px solid #ccc ; } </style > </head > <body > <nav > <a data-path ="/" > 主页</a > <a data-path ="/about" > 关于我们</a > <a data-path ="/contact" > 联系方式</a > </nav > <div id ="app-root" > </div > </body > <script > const appRoot = document .getElementById ('app-root' ); function render (path ) { let content = '' ; switch (path) { case '/about' : content = '<h1>关于我们</h1><p>我们是一个追求极致的笔记平台</p>' ; break ; case '/contact' : content = '<h1>联系方式</h1><p>请发送邮件至 3381292732@qq.com</p>' ; break ; default : content = '<h1>主页</h1><p>欢迎来到我们的网站!</p>' ; } appRoot.innerHTML = content; } document .querySelector ("nav" ).addEventListener ("click" , (e ) => { if (e.target .tagName === "A" ) { e.preventDefault (); const path = e.target .dataset .path ; history.pushState ({ path : path }, '' , path); render (path); } }); window .addEventListener ('popstate' , (e ) => { const path = e.state ? e.state .path : '/' ; render (path); }); render (location.pathname ); </script > </html >
10.3. 客户端数据持久化:让网页拥有“记忆” 核心场景 : HTTP 协议本身是 无状态 的,这意味着服务器默认不会“记住”你的上一次访问。然而,现代 Web 应用充满了需要“记忆”的场景:
用户关闭了浏览器,下次打开时,网站依然保持着他的登录状态。 用户将商品加入了购物车,刷新页面后,商品依然还在。 用户填写一个复杂的表单,中途不小心关闭了标签页,重新打开后,之前填写的内容还在。 为了解决这些问题,浏览器提供了多种客户端存储技术,让我们的网页能够在用户的设备上持久化数据。
10.3.1. Web Storage API:现代客户端存储方案 Web Storage API 是 HTML5 引入的,旨在提供比 Cookie 更简单、更强大的客户端存储方案。它分为 localStorage
和 sessionStorage
两种。
localStorage
: 永久的本地存储localStorage
用于 永久性 地在本地存储数据。除非用户手动清除浏览器缓存或代码主动删除,否则数据将永远存在,即使关闭浏览器或电脑重启。
API 方法 描述 setItem(key, value)
存储一个键值对。 getItem(key)
根据键读取一个值。 removeItem(key)
根据键删除一个键值对。 clear()
清空所有存储的数据。
核心原理 : localStorage
只能存储 字符串 。如果要存储对象或数组,必须先用 JSON.stringify()
将其转换为 JSON 字符串,读取时再用 JSON.parse()
解析回来。
示例:实现一个可记忆的主题切换器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <style > body .dark-theme { background-color : #333 ; color : #eee ; } #theme-switcher { padding : 10px ; } </style > </head > <body > <button id ="theme-switcher" > 切换主题</button > <p > 当前主题会永久保存在你的浏览器中。</p > </body > <script > const switcherBtn = document .getElementById ('theme-switcher' ); function applyTheme (theme ) { if (theme === 'dark' ) { document .body .classList .add ('dark-theme' ); } else { document .body .classList .remove ('dark-theme' ); } } const savedTheme = localStorage .getItem ('theme' ); if (savedTheme) { applyTheme (savedTheme); } switcherBtn.addEventListener ('click' , () => { const currentTheme = document .body .classList .contains ('dark-theme' ) ? 'dark' : 'light' ; const newTheme = currentTheme === 'dark' ? 'light' : 'dark' ; applyTheme (newTheme); localStorage .setItem ('theme' , newTheme); }); </script > </html >
请尝试点击“切换主题”按钮,然后点击 sandbox
右上角的“重新运行”图标(模拟刷新页面),你会发现主题状态被成功保留了。
sessionStorage
: 基于会话的临时存储sessionStorage
的 API 与 localStorage
完全相同 ,但其生命周期完全不同:
生命周期 : sessionStorage
中存储的数据只在 当前浏览器标签页的会话期间 有效。一旦该标签页或浏览器被关闭,数据就会被清除。作用域 : 数据只在当前标签页可见,在另一个标签页中打开同一个网站,也无法访问到。核心应用场景 : sessionStorage
非常适合存储一些 一次性的、临时的 会话数据,例如防止用户在填写多步骤表单时不小心刷新页面而导致数据丢失。
10.3.2. Cookie
: 与服务器通信的信使 Cookie
是最传统的客户端存储技术。与 Web Storage 的主要区别在于,Cookie
的核心使命是 在客户端和服务器之间传递信息 。
核心原理 : 一旦为一个域名设置了 Cookie,那么在后续每一次向该域名发送 HTTP 请求时,浏览器都会 自动 在请求头中带上这些 Cookie。服务器可以读取这些 Cookie 来识别用户、维持会话等。
Cookie
属性描述 Expires
/ Max-Age
设置 Cookie 的过期时间。 Path
指定 Cookie 生效的路径。 Domain
指定 Cookie 生效的域名。 Secure
(安全) 设置后,Cookie 只会在 HTTPS 连接中被发送。HttpOnly
(安全) 设置后,此 Cookie 无法被 JavaScript 访问 (document.cookie
),只能由服务器读写。这是防止 XSS 攻击窃取用户会话 Cookie 的关键防御手段。SameSite
(安全) 控制 Cookie 是否随跨站请求发送,是防御 CSRF 攻击的核心机制。
JavaScript 操作 :
1 2 3 4 5 6 7 8 9 10 11 12 const expiresDate = new Date (Date .now () + 7 * 24 * 60 * 60 * 1000 ).toUTCString ();document .cookie = `username=Prorise; expires=${expiresDate} ; path=/; SameSite=Lax` ;const cookies = document .cookie .split ('; ' ).reduce ((acc, cookie ) => { const [key, value] = cookie.split ('=' ); acc[key] = value; return acc; }, {}); console .log ('当前页面的 Cookies:' , cookies);
1 当前页面的 Cookies: { username: "Prorise" }
10.3.3. 存储方案对比与选型 特性 localStorage
sessionStorage
Cookie
生命周期 永久 单个会话(标签页) 可设置过期时间 容量大小 5MB ~ 10MB 5MB ~ 10MB 约 4KB 与服务器通信 不参与 不参与 自动 随每次请求发送API 易用性 非常简单 (setItem
, getItem
) 非常简单 (setItem
, getItem
) 繁琐,需手动解析字符串 核心应用场景 长期用户设置,离线数据 临时表单数据,单页应用状态 身份认证令牌 (Token) ,会话 ID选型建议 :
需要长期保留在客户端,且不常与服务器交互的数据(如用户偏好设置、主题) -> 使用 localStorage
。仅在单次浏览会话中需要暂存的数据(如复杂表单的草稿) -> 使用 sessionStorage
。需要与服务器进行身份验证或状态保持的数据(如登录令牌 Session ID) -> 使用 Cookie
,并务必设置好 HttpOnly
, Secure
, SameSite
等安全属性。10.4. 环境感知与特性检测:编写健壮的跨平台代码 核心场景 : 一个专业的 Web 应用,不应假设它运行在何种设备或浏览器上。用户的设备可能是高性能台式机,也可能是低性能手机;浏览器可能是最新的 Chrome,也可能是其他内核的浏览器。为了提供一致且可靠的用户体验,我们的代码必须具备“感知”其运行环境并作出相应调整的能力。本节,我们将学习如何利用 navigator
和 screen
对象来实现这一点。
10.4.1. navigator
对象:探测浏览器与设备能力 window.navigator
对象是一个信息仓库,存储了关于浏览器本身及其所在环境的详细信息。
传统方式:用户代理嗅探 (User Agent Sniffing) 过去,开发者常常通过解析 navigator.userAgent
这个包含了浏览器、引擎、操作系统等信息的长字符串,来判断用户正在使用的浏览器。
1 2 3 4 5 6 7 8 9 const ua = navigator.userAgent ;console .log ('你的 User Agent:' , ua);if (ua.includes ('Chrome' )) { console .log ('检测到 Chrome 浏览器。' ); } else if (ua.includes ('Firefox' )) { console .log ('检测到 Firefox 浏览器。' ); }
1 2 你的 User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 检测到 Chrome 浏览器。
不要这样做! 用户代理嗅探是一种 脆弱且已被废弃 的做法。原因在于:
User Agent 字符串非常复杂且可能被用户或插件修改; 新的浏览器层出不穷,维护一个准确的检测列表几乎不可能。 现代最佳实践:特性检测 (Feature Detection) 现代 Web 开发遵循一个核心原则:我们不应该关心“用户在用什么浏览器”,而应该关心“用户的浏览器支持什么功能”。 这就是特性检测。我们通过检查一个特定的 API 或属性是否存在,来决定是否使用它。
示例:安全地使用现代 API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const copyBtn = document .getElementById ('copy-btn' );copyBtn.addEventListener ('click' , () => { if (navigator.clipboard && navigator.clipboard .writeText ) { navigator.clipboard .writeText ('这是通过现代 API 复制的!' ) .then (() => alert ('已成功复制到剪贴板!' )) .catch (err => console .error ('复制失败:' , err)); } else { alert ('你的浏览器不支持自动复制功能,请手动复制。' ); } }); function updateOnlineStatus ( ) { const statusEl = document .getElementById ('status' ); statusEl.textContent = navigator.onLine ? '设备在线' : '设备离线' ; statusEl.style .color = navigator.onLine ? 'green' : 'red' ; } window .addEventListener ('online' , updateOnlineStatus);window .addEventListener ('offline' , updateOnlineStatus);updateOnlineStatus ();
10.4.2. screen
对象:获取屏幕信息 window.screen
对象提供了关于用户显示器屏幕的信息,这对于需要进行窗口管理或收集分析数据的应用非常有用。
属性 描述 screen.width
/ height
用户屏幕的 完整 宽度和高度(以像素为单位)。 screen.availWidth
/ availHeight
用户屏幕的 可用 宽度和高度,即减去了操作系统界面组件(如 Windows 任务栏或 macOS 菜单栏)后的空间。 screen.colorDepth
返回屏幕的颜色深度(通常是 24 或 32)。 screen.pixelDepth
返回屏幕的像素深度(现代设备上通常与 colorDepth
相同)。
示例:获取屏幕信息并打开一个适配的窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <button id ="open-window-btn" > 打开一个适配窗口</button > <pre id ="screen-info" > </pre > <script > const infoEl = document .getElementById ('screen-info' ); const openBtn = document .getElementById ('open-window-btn' ); const screenInfo = ` 完整分辨率: ${screen.width} x ${screen.height} 可用空间: ${screen.availWidth} x ${screen.availHeight} 颜色深度: ${screen.colorDepth} bits ` ; infoEl.textContent = screenInfo.trim (); openBtn.addEventListener ('click' , () => { const popupWidth = screen.availWidth * 0.8 ; const popupHeight = screen.availHeight * 0.8 ; window .open ( 'https://prorise666.site' , '/' , `width=${popupWidth} ,height=${popupHeight} ` ); }); </script >
window.open
可能会被浏览器的弹出窗口拦截器阻止。在实际应用中,通常需要由明确的用户操作(如点击)来触发。
10.5. 2025+ 现代 Web API 精选 承上启下 : 传统的网页能力有限,但随着 Web 平台的发展,浏览器正不断地向 JavaScript 开放更多与操作系统底层交互的能力。这些现代 Web API 正在模糊网页与原生应用 (Native App) 之间的界限,让 Web 应用能够实现更丰富、更强大的功能。本节将精选介绍几个在 2025 年极具实用价值的现代 API。
10.5.1. Clipboard API (剪贴板 API) 核心痛点 : 传统的 document.execCommand('copy')
方法是同步的,API 不友好,且在安全策略日益收紧的现代浏览器中,其行为变得不可靠。我们需要一种更安全、更强大的方式来与系统剪贴板交互。
解决方案 : 现代的 Clipboard API
(navigator.clipboard
) 是基于 Promise
的、异步的,并且与浏览器的权限系统紧密集成,提供了更安全、更强大的读写能力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <style > #clipboard-demo textarea { width : 95% ; height : 60px ; margin-bottom : 10px ; } #clipboard-demo button { margin-right : 10px ; } </style > <div id ="clipboard-demo" > <textarea id ="text-to-copy" > 这是你想要复制的文本。</textarea > <button id ="copy-btn" > 复制</button > <button id ="paste-btn" > 粘贴</button > <p id ="clipboard-status" > </p > </div > <script > const textEl = document .getElementById ('text-to-copy' ); const copyBtn = document .getElementById ('copy-btn' ); const pasteBtn = document .getElementById ('paste-btn' ); const statusEl = document .getElementById ('clipboard-status' ); copyBtn.addEventListener ('click' , () => { navigator.clipboard .writeText (textEl.value ) .then (() => { statusEl.textContent = '状态:已成功复制到剪贴板!' ; console .log ('复制成功' ); }) .catch (err => { statusEl.textContent = '状态:复制失败,请检查浏览器权限。' ; console .error ('复制失败:' , err); }); }); pasteBtn.addEventListener ('click' , () => { navigator.clipboard .readText () .then (clipboardText => { textEl.value = clipboardText; statusEl.textContent = '状态:已成功从剪贴板粘贴!' ; console .log ('粘贴成功' ); }) .catch (err => { statusEl.textContent = '状态:粘贴失败,请检查浏览器权限。' ; console .error ('粘贴失败:' , err); }); }); </script >
出于安全考虑,浏览器通常会在首次调用剪贴板 API 时向用户请求权限。此外,读取剪贴板的操作通常要求页面处于激活状态。
10.5.2. Page Visibility API (页面可见性 API) 核心痛点 : 用户打开了 20 个浏览器标签页,我们的页面在后台不可见,但页面上的轮播图动画、轮询服务器的请求却仍在消耗着用户的 CPU、电量和网络资源。
解决方案 : Page Visibility API
允许我们的页面知道自己当前是否对用户可见。我们可以通过监听 document
上的 visibilitychange
事件,并检查 document.hidden
(布尔值) 属性,来智能地暂停或恢复耗时任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <style > #spinner { width : 50px ; height : 50px ; border : 5px solid #f3f3f3 ; border-top : 5px solid #3498db ; border-radius : 50% ; animation : spin 2s linear infinite; } @keyframes spin { 0% { transform : rotate (0deg ); } 100% { transform : rotate (360deg ); } } #spinner .paused { animation-play-state : paused; } </style > <div id ="spinner" > </div > <p id ="visibility-status" > 当前页面可见,动画正在播放。</p > <script > const spinner = document .getElementById ('spinner' ); const status = document .getElementById ('visibility-status' ); document .addEventListener ('visibilitychange' , () => { if (document .hidden ) { spinner.classList .add ('paused' ); status.textContent = '页面已隐藏,动画暂停以节省资源。' ; console .log ('页面隐藏于' , new Date ().toLocaleTimeString ()); } else { spinner.classList .remove ('paused' ); status.textContent = '当前页面可见,动画正在播放。' ; console .log ('页面恢复于' , new Date ().toLocaleTimeString ()); } }); </script >
请尝试切换到另一个浏览器标签页,等待几秒钟,然后再切换回来,观察动画和文本的变化。
10.5.3. Web Share API (网页分享 API) 核心痛点 : 在移动设备上,用户希望像分享原生 App 内容一样,方便地将网页分享到微信、短信或任何其他 App。传统的做法是为每个社交平台都做一个分享按钮,体验差且不完整。
解决方案 : Web Share API
(navigator.share()
) 允许网页调用操作系统的 原生分享对话框 ,让用户可以选择任意已安装的应用进行分享。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <button id ="share-btn" > 分享本页</button > <p id ="share-result" > </p > <script > const shareBtn = document .getElementById ('share-btn' ); const shareResult = document .getElementById ('share-result' ); shareBtn.addEventListener ('click' , async () => { if (!navigator.share ) { shareResult.textContent = '你的浏览器不支持 Web Share API。' ; return ; } const shareData = { title : 'Prorise 技术笔记' , text : '快来看看这个超棒的笔记平台!' , url : 'https://prorise.site' }; try { await navigator.share (shareData); shareResult.textContent = '感谢你的分享!' ; } catch (err) { shareResult.textContent = `分享失败: ${err.message} ` ; } }); </script >
Web Share API
必须在 安全上下文 (HTTPS) 中,并且通常需要由 明确的用户操作 (如 click
事件)来触发。在 sandbox
中可能无法调用,但在真实的移动端浏览器上会弹出原生分享界面。
10.5.4. Permissions API (权限 API) 核心痛点 : 在需要使用地理位置、通知等敏感功能前,如果我们能预先知道用户当前的授权状态(是“已授权”、“已拒绝”还是“需要询问”),就可以提供更友好的用户体验,而不是唐突地弹出一个权限请求框。
解决方案 : Permissions API
(navigator.permissions.query()
) 提供了一种标准化的方式来查询用户对特定功能的授权状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <button id ="geo-btn" > 请求地理位置</button > <p id ="permission-status" > 权限状态:未知</p > </body > <script > const geoBtn = document .getElementById ('geo-btn' ); const statusEl = document .getElementById ('permission-status' ); async function checkGeoPermission ( ) { try { const permissionStatus = await navigator.permissions .query ({ name : 'geolocation' }); console .log (permissionStatus.state ) statusEl.textContent = `权限状态: ${permissionStatus.state} ` ; if (permissionStatus.state === 'prompt' ) { navigator.geolocation .getCurrentPosition ( (position ) => { statusEl.textContent = `权限已授予,位置: ${position.coords.latitude} , ${position.coords.longitude} ` ; }, (error ) => { statusEl.textContent = `权限被拒绝或出错: ${error.message} ` ; } ); } permissionStatus.onchange = () => { statusEl.textContent = `权限状态已变为: ${permissionStatus.state} ` ; }; } catch (err) { statusEl.textContent = `无法查询权限: ${err.message} ` ; } } geoBtn.addEventListener ('click' , checkGeoPermission); </script > </html >