BOM(Browser Object Model)是浏览器提供的对象模型,用于与浏览器窗口进行交互。BOM 的核心是 window 对象。
🎯 window 对象#
全局作用域#
// window 是全局对象var globalVar = '全局变量'console.log(window.globalVar) // '全局变量'
// 全局函数也是 window 的属性function globalFunc() { return 'hello'}console.log(window.globalFunc()) // 'hello'
// 🔶 let/const 不会成为 window 属性let letVar = 'let 变量'console.log(window.letVar) // undefined窗口尺寸#
// 视口尺寸(不含滚动条)window.innerWidth // 视口宽度window.innerHeight // 视口高度
// 窗口尺寸(含工具栏等)window.outerWidth // 窗口总宽度window.outerHeight // 窗口总高度
// 文档尺寸document.documentElement.clientWidth // 文档宽度document.documentElement.clientHeight // 文档高度
// 屏幕尺寸screen.width // 屏幕宽度screen.height // 屏幕高度screen.availWidth // 可用宽度(排除任务栏)screen.availHeight // 可用高度
// 滚动位置window.scrollX // 水平滚动距离window.scrollY // 垂直滚动距离// 或window.pageXOffsetwindow.pageYOffset滚动控制#
// 滚动到指定位置window.scrollTo(0, 100) // 滚动到 y=100window.scrollTo({ top: 100, left: 0, behavior: 'smooth', // 平滑滚动})
// 相对滚动window.scrollBy(0, 100) // 向下滚动 100pxwindow.scrollBy({ top: 100, behavior: 'smooth',})
// 滚动元素到视图element.scrollIntoView()element.scrollIntoView({ behavior: 'smooth', block: 'center' })弹窗方法#
// 警告框window.alert('提示信息')
// 确认框const confirmed = window.confirm('确定删除吗?')if (confirmed) { // 用户点击了确定}
// 输入框const name = window.prompt('请输入姓名', '默认值')if (name !== null) { console.log('输入:', name)}
// 🔶 这些方法会阻塞 JavaScript 执行// 现代开发中尽量避免使用,改用自定义弹窗打开新窗口#
// 打开新窗口/标签页const newWindow = window.open('https://example.com', '_blank')
// 指定窗口大小和位置window.open( 'https://example.com', 'popup', 'width=400,height=300,left=100,top=100')
// 关闭窗口newWindow.close()
// 检查窗口是否关闭if (newWindow.closed) { console.log('窗口已关闭')}
// 🔶 弹窗可能被浏览器拦截// 最好由用户操作触发(如点击事件)location 对象#
URL 信息#
// 假设当前 URL: https://example.com:8080/path/page.html?id=123#section
location.href // 完整 URLlocation.protocol // 'https:'location.host // 'example.com:8080'location.hostname // 'example.com'location.port // '8080'location.pathname // '/path/page.html'location.search // '?id=123'location.hash // '#section'location.origin // 'https://example.com:8080'页面跳转#
// 方式1:设置 href(会留下历史记录)location.href = 'https://example.com'
// 方式2:使用 assign(等同于设置 href)location.assign('https://example.com')
// 方式3:使用 replace(不留历史记录)location.replace('https://example.com')
// 刷新页面location.reload() // 普通刷新location.reload(true) // 强制刷新(从服务器)
// 只改变 hash(不刷新页面)location.hash = '#new-section'URL 参数解析#
// 获取查询参数const params = new URLSearchParams(location.search)
// 获取单个参数params.get('id') // '123'params.get('name') // null(不存在)
// 检查参数是否存在params.has('id') // true
// 获取所有参数for (const [key, value] of params) { console.log(`${key}: ${value}`)}
// 获取同名参数的所���值params.getAll('tags') // ['a', 'b'](如果有 ?tags=a&tags=b)
// 转为对象const paramsObj = Object.fromEntries(params)
// 修改参数params.set('id', '456')params.append('new', 'value')params.delete('id')
// 转回字符串params.toString() // 'new=value'构建 URL#
// 使用 URL 对象const url = new URL('https://example.com/path')url.searchParams.set('id', '123')url.searchParams.set('name', '张三')url.hash = 'section'
console.log(url.href)// 'https://example.com/path?id=123&name=%E5%BC%A0%E4%B8%89#section'
// 相对 URLconst relative = new URL('/api/users', 'https://example.com')console.log(relative.href) // 'https://example.com/api/users'history 对象#
历史导航#
// 历史记录长度history.length
// 后退history.back() // 等同于点击后退按钮
// 前进history.forward() // 等同于点击前进按钮
// 跳转到指定历史history.go(-1) // 后退一页history.go(1) // 前进一页history.go(-2) // 后退两页history.go(0) // 刷新当前页History API(SPA 路由)#
// 添加历史记录(不刷新页面)history.pushState( { page: 1 }, // state 对象 '', // title(大多数浏览器忽略) '/page/1' // URL)
// 替换当前历史记录history.replaceState({ page: 2 }, '', '/page/2')
// 获取当前状态console.log(history.state) // { page: 2 }
// 监听前进/后退window.addEventListener('popstate', (event) => { console.log('导航到:', location.pathname) console.log('状态:', event.state)})
// 🔶 注意:pushState 不会触发 popstate// 只有浏览器的前进/后退按钮会触发简单路由实现#
class Router { constructor() { this.routes = {} window.addEventListener('popstate', () => this.handleRoute()) }
on(path, handler) { this.routes[path] = handler }
navigate(path, state = {}) { history.pushState(state, '', path) this.handleRoute() }
handleRoute() { const path = location.pathname const handler = this.routes[path] if (handler) { handler(history.state) } }}
// 使用const router = new Router()
router.on('/', () => console.log('首页'))router.on('/about', () => console.log('关于页'))router.on('/user', (state) => console.log('用户��', state))
router.navigate('/user', { id: 123 })navigator 对象#
浏览器信息#
// 用户代理字符串navigator.userAgent// 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'
// 浏览器名称和版本navigator.appName // 'Netscape'(历史遗留)navigator.appVersion // 版本信息
// 平台navigator.platform // 'Win32', 'MacIntel', 'Linux x86_64'
// 语言navigator.language // 'zh-CN'navigator.languages // ['zh-CN', 'zh', 'en']
// 是否在线navigator.onLine // true/false
// CPU 核心数navigator.hardwareConcurrency // 8
// 设备内存(GB)navigator.deviceMemory // 8特性检测#
// 🔶 不推荐使用 userAgent 检测浏览器// ✅ 推荐使用特性检测
// 检测是否支持某个 APIif ('geolocation' in navigator) { // 支持地理定位}
if ('serviceWorker' in navigator) { // 支持 Service Worker}
if ('share' in navigator) { // 支持 Web Share API}
if ('clipboard' in navigator) { // 支持剪贴板 API}
// 检测移动设备(使用媒体查询)const isMobile = window.matchMedia('(max-width: 768px)').matches
// 或检测触摸支持const hasTouch = 'ontouchstart' in window常用 API#
// 地理定位navigator.geolocation.getCurrentPosition( (position) => { console.log('纬度:', position.coords.latitude) console.log('经度:', position.coords.longitude) }, (error) => { console.error('定位失败:', error.message) })
// 剪贴板await navigator.clipboard.writeText('复制的文本')const text = await navigator.clipboard.readText()
// 分享if (navigator.share) { await navigator.share({ title: '标题', text: '描述', url: 'https://example.com', })}
// 震动(移动设备)navigator.vibrate(200) // 震动 200msnavigator.vibrate([100, 50, 100]) // 震动-暂停-震动screen 对象#
// 屏幕尺寸screen.width // 屏幕宽度screen.height // 屏幕高度
// 可用尺寸(排除任务栏等)screen.availWidth // 可用宽度screen.availHeight // 可用��度
// 颜色深度screen.colorDepth // 24(位)screen.pixelDepth // 24
// 屏幕方向(移动设备)screen.orientation.type // 'portrait-primary', 'landscape-primary'screen.orientation.angle // 0, 90, 180, 270
// 监听方向变化screen.orientation.addEventListener('change', () => { console.log('方向:', screen.orientation.type)})实际应用#
响应式检测#
// 使用媒体查询const mediaQuery = window.matchMedia('(max-width: 768px)')
function handleMediaChange(e) { if (e.matches) { console.log('移动端视图') } else { console.log('桌面端视图') }}
mediaQuery.addEventListener('change', handleMediaChange)handleMediaChange(mediaQuery) // 初始检查全屏 API#
// 进入全屏async function enterFullscreen(element = document.documentElement) { try { await element.requestFullscreen() } catch (error) { console.error('全屏失败:', error) }}
// 退出全屏function exitFullscreen() { if (document.fullscreenElement) { document.exitFullscreen() }}
// 检查是否全屏document.fullscreenElement // 当前全屏元素或 null
// 监听全屏变化document.addEventListener('fullscreenchange', () => { console.log('全屏状态:', !!document.fullscreenElement)})页面可见性#
// 检测页面是否可见document.visibilityState // 'visible', 'hidden', 'prerender'document.hidden // true/false
// 监听可见性变化document.addEventListener('visibilitychange', () => { if (document.hidden) { console.log('页面隐藏') // 暂停视频、动画等 } else { console.log('页面可见') // 恢复操作 }})离开页面提示#
// 提示用户有未保存的更改let hasUnsavedChanges = false
window.addEventListener('beforeunload', (event) => { if (hasUnsavedChanges) { event.preventDefault() event.returnValue = '' // 显示确认对话框 }})
// 标记有未保存更改input.addEventListener('change', () => { hasUnsavedChanges = true})
// 保存后清除标记function save() { // 保存逻辑 hasUnsavedChanges = false}总结#
| 对象 | 用途 |
|---|---|
window | 全局对象,窗口控制 |
location | URL 信息和页面跳转 |
history | 历史记录管理 |
navigator | 浏览器/设备信息 |
screen | 屏幕信息 |
| 常用操作 | 方法 |
|---|---|
| 页面跳转 | location.href = url |
| 刷新页面 | location.reload() |
| 后退 | history.back() |
| 前进 | history.forward() |
| 添加历史 | history.pushState() |
| 获取参数 | new URLSearchParams(location.search) |
核心要点:
- window 是 BOM 的核心,也是全局对象
- location 处理 URL 相关操作
- history API 是 SPA 路由的基础
- navigator 用于获取浏览器和设备信息
- 使用特性检测而非 userAgent 判断