目录
BOM(Browser Object Model,浏览器对象模型)是 JavaScript 与浏览器交互的核心接口,它允许开发者控制浏览器窗口、导航、历史记录、设备信息等关键功能。与 DOM(文档对象模型)专注于网页内容不同,BOM 聚焦于浏览器本身的特性。本文将在基础内容上进行深度扩展,涵盖更多实用细节、高级用法及实际开发场景。
一、window 对象:BOM 的核心
window
是 BOM 的顶层对象,代表浏览器窗口或标签页,同时也是 JavaScript 全局作用域的载体——所有全局变量、函数和对象都是 window
的属性。
1. 窗口层级与关系
在包含框架(iframe
)的页面中,window
对象存在层级关系,通过以下属性可访问不同层级的窗口:
// 当前窗口自身
console.log(window.self); // 等同于 window
// 父窗口(若当前窗口是框架)
console.log(window.parent);
// 顶层窗口(最外层窗口,无父窗口)
console.log(window.top);
// 检查当前窗口是否是顶层窗口(防止被嵌套在恶意框架中)
if (window.top !== window.self) {
// 可能被嵌套在第三方网站,可执行防御逻辑
window.top.location.href = window.self.location.href; // 跳转到顶层
}
// 获取页面中所有框架的窗口对象
const frames = window.frames; // 类数组对象,包含所有 iframe 窗口
应用场景:防止网页被恶意网站通过 iframe
嵌套(点击劫持攻击),可通过检测 window.top
与 window.self
是否一致实现防御。
2. 窗口尺寸与滚动
除基础尺寸属性外,还需掌握滚动相关的操作,这在处理长页面或动态加载内容时至关重要:
尺寸细节
// 窗口内部尺寸(含滚动条,不含工具栏)
const innerSize = {
width: window.innerWidth,
height: window.innerHeight
};
// 窗口外部尺寸(含边框、工具栏,不同浏览器定义有差异)
const outerSize = {
width: window.outerWidth,
height: window.outerHeight
};
// 文档内容尺寸(实际内容高度,可能超过窗口高度)
const docSize = {
width: Math.max(
document.body.scrollWidth,
document.body.offsetWidth,
document.documentElement.clientWidth,
document.documentElement.scrollWidth,
document.documentElement.offsetWidth
),
height: Math.max(
document.body.scrollHeight,
document.body.offsetHeight,
document.documentElement.clientHeight,
document.documentElement.scrollHeight,
document.documentElement.offsetHeight
)
};
滚动操作
// 获取文档滚动位置(兼容不同浏览器)
const scrollPos = {
x: window.scrollX || document.documentElement.scrollLeft,
y: window.scrollY || document.documentElement.scrollTop
};
// 滚动到指定位置(绝对位置)
window.scrollTo(0, 500); // 滚动到 Y 轴 500px 处
// 或使用配置对象(支持平滑滚动)
window.scrollTo({
top: 500,
behavior: 'smooth' // 平滑滚动
});
// 相对当前位置滚动
window.scrollBy(0, 100); // 向下滚动 100px
// 滚动到指定元素(需元素 ID 或 DOM 对象)
document.getElementById('target').scrollIntoView({
behavior: 'smooth',
block: 'start' // 对齐元素顶部
});
注意:scrollX
/scrollY
是标准属性,scrollLeft
/scrollTop
是兼容旧浏览器的写法,现代开发中推荐优先使用标准属性。
3. 窗口操作深度解析
打开新窗口(window.open
)
window.open
用于创建新窗口,其参数和行为有诸多细节需注意:
// 完整参数示例
const newWindow = window.open(
'https://example.com', // URL(空字符串则打开空白页)
'newWindowName', // 窗口名称(可用于定位已存在窗口)
// 窗口特征字符串(逗号分隔,无空格)
'width=800,height=600,top=100,left=100,' +
'toolbar=no,menubar=no,scrollbars=yes,' +
'resizable=yes,location=yes,status=yes'
);
常用特征参数:
width
/height
:窗口宽高(像素)top
/left
:窗口相对于屏幕的坐标toolbar
/menubar
/location
:是否显示工具栏/菜单栏/地址栏(yes
/no
)scrollbars
:是否允许滚动(yes
则内容超出时显示滚动条)resizable
:是否允许调整窗口大小noopener
:新窗口的window.opener
为null
(增强安全性,防止钓鱼)noreferrer
:不发送 Referrer 头(隐私保护)
安全限制:
- 现代浏览器默认阻止非用户交互(如
load
事件中)触发的window.open
(视为弹窗广告) - 仅能对
window.open
创建的窗口执行moveTo
/resizeTo
等操作,主窗口通常受浏览器限制
全屏 API 扩展
除基础的全屏切换,还需掌握全屏状态检测和事件监听:
// 检测当前是否处于全屏状态
function isFullscreen() {
return !!(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement
);
}
// 监听全屏状态变化
document.addEventListener('fullscreenchange', () => {
if (isFullscreen()) {
console.log('进入全屏模式');
} else {
console.log('退出全屏模式');
}
});
// 全屏错误处理
document.addEventListener('fullscreenerror', (err) => {
console.error('全屏操作失败:', err);
});
二、location 对象:URL 与导航控制
location
对象封装了当前页面的 URL 信息,并提供导航相关的方法,是单页应用(SPA)路由管理的核心工具。
1. URL 解析与操作
完整 URL 结构
以 https://user:pass@example.com:8080/path/file.html?name=test#section1
为例:
protocol
:https:
(协议)username
:user
(用户名,现代浏览器很少使用)password
:pass
(密码,已废弃,不安全)hostname
:example.com
(域名)port
:8080
(端口,默认端口可省略)host
:example.com:8080
(hostname + port
)pathname
:/path/file.html
(路径)search
:?name=test
(查询字符串)hash
:#section1
(锚点)href
:完整 URL 字符串
高级查询参数处理
使用 URLSearchParams
API 高效操作查询字符串(替代手动解析):
// 解析现有查询参数
const params = new URLSearchParams(location.search);
// 获取参数
console.log(params.get('name')); // "test"(不存在则返回 null)
console.log(params.getAll('tags')); // 获取多值参数(如 ?tags=js&tags=bom)
// 添加/修改参数
params.set('page', '2'); // 覆盖现有参数
params.append('filter', 'active'); // 新增参数(允许重复)
// 删除参数
params.delete('name');
// 转换为查询字符串(带 ?)
const newSearch = '?' + params.toString(); // "?page=2&filter=active"
// 应用到当前 URL(不刷新页面,修改地址栏)
history.replaceState(null, '', newSearch);
注意:URLSearchParams
不支持嵌套对象,复杂参数需手动序列化(如 JSON.stringify)。
2. 导航方法对比
方法 | 作用 | 历史记录 | 适用场景 |
---|---|---|---|
location.href = url | 跳转到新 URL | 新增记录 | 常规导航 |
location.assign(url) | 同 href | 新增记录 | 与 href 等效,更语义化 |
location.replace(url) | 替换当前页面 | 不新增记录 | 避免返回原页面(如登录后替换登录页) |
location.reload(force) | 重新加载 | 无变化 | 刷新页面,force=true 强制从服务器加载 |
location.hash = 'id' | 跳转到锚点 | 新增记录(部分浏览器) | 页面内导航 |
三、history 对象:历史记录管理
history
对象提供对浏览器历史记录栈的访问,是 SPA 路由实现的核心,通过它可在不刷新页面的情况下修改 URL。
1. 核心方法与事件
pushState
与 replaceState
// 添加新历史记录(不刷新页面)
history.pushState(
{ page: 'product', id: 123 }, // state 对象(可存储页面状态)
'产品详情页', // 标题(多数浏览器忽略)
'/product?id=123' // 新 URL(必须与当前 URL 同源)
);
// 替换当前历史记录
history.replaceState(
{ page: 'product', id: 123, filter: 'new' },
'产品详情页(筛选)',
'/product?id=123&filter=new'
);
state 对象注意事项:
- 会被序列化存储,建议仅存简单数据(字符串、数字、数组、对象)
- 大小限制:不同浏览器限制不同(通常 64KB 左右)
- 刷新页面后需重新初始化(state 会丢失)
popstate
事件
仅在通过浏览器前进/后退按钮或 history.back()
/forward()
/go()
触发历史记录变化时触发:
window.addEventListener('popstate', (event) => {
console.log('历史记录变化:', event.state);
// 根据 state 数据更新页面内容(SPA 路由核心逻辑)
if (event.state?.page === 'product') {
renderProductPage(event.state.id);
}
});
常见误区:pushState
和 replaceState
不会触发 popstate
事件,需手动同步页面状态。
2. SPA 路由应用示例
// 路由配置
const routes = {
'/': () => renderHome(),
'/about': () => renderAbout(),
'/product/:id': (params) => renderProduct(params.id)
};
// 初始化路由
function initRouter() {
// 处理初始 URL
handleRoute();
// 监听历史记录变化
window.addEventListener('popstate', handleRoute);
}
// 处理路由
function handleRoute() {
const path = location.pathname;
// 匹配路由并执行对应渲染函数
for (const [route, handler] of Object.entries(routes)) {
const match = path.match(parseRoute(route));
if (match) {
handler(match.groups); // 传递参数
return;
}
}
// 404 页面
renderNotFound();
}
// 跳转路由(不刷新页面)
function navigateTo(url, state = {}) {
history.pushState(state, '', url);
handleRoute(); // 手动触发路由更新
}
// 初始化
initRouter();
四、navigator 对象:浏览器与设备信息
navigator
对象提供浏览器和设备的详细信息,常用于特性检测、用户分析和兼容性处理。
1. 实用属性扩展
// 检测 Cookie 是否启用
console.log('Cookie 启用:', navigator.cookieEnabled);
// 设备内存(GB,近似值)
console.log('设备内存:', navigator.deviceMemory);
// CPU 核心数
console.log('CPU 核心数:', navigator.hardwareConcurrency);
// 检测是否支持触摸
console.log('支持触摸:', 'ontouchstart' in window);
// 检测是否支持 WebGL
console.log('支持 WebGL:', 'WebGLRenderingContext' in window);
// 浏览器语言(用户首选语言)
console.log('首选语言:', navigator.language); // 如 "zh-CN"
console.log('所有语言:', navigator.languages); // 如 ["zh-CN", "en-US"]
// 浏览器是否处于在线状态
console.log('是否在线:', navigator.onLine);
window.addEventListener('online', () => console.log('恢复在线'));
window.addEventListener('offline', () => console.log('已离线'));
2. 地理定位高级用法
除单次获取位置,还可持续监听位置变化:
// 持续监听位置
let watchId;
if (navigator.geolocation) {
watchId = navigator.geolocation.watchPosition(
(position) => {
console.log('当前位置:', {
lat: position.coords.latitude,
lng: position.coords.longitude,
accuracy: position.coords.accuracy // 精度(米)
});
},
(error) => {
console.error('位置错误:', error.message);
// 错误类型:PERMISSION_DENIED(1)、POSITION_UNAVAILABLE(2)、TIMEOUT(3)
},
{
enableHighAccuracy: false, // 高精度模式(更耗电)
timeout: 5000, // 超时时间(毫秒)
maximumAge: 300000 // 位置缓存有效期(5分钟)
}
);
}
// 停止监听
// navigator.geolocation.clearWatch(watchId);
五、screen 对象:屏幕信息
screen
对象提供用户设备屏幕的硬件信息,常用于响应式布局和适配不同设备。
关键属性解析
属性 | 含义 | 注意事项 |
---|---|---|
width /height | 屏幕总宽高(像素) | 含任务栏/Dock 栏区域 |
availWidth /availHeight | 可用宽高(像素) | 不含任务栏/Dock 栏,实际可显示区域 |
colorDepth | 颜色深度(位/像素) | 通常为 24 或 32(真彩色) |
pixelDepth | 像素深度(位/像素) | 与 colorDepth 通常一致,表示屏幕硬件像素密度 |
orientation | 屏幕方向 | 返回 ScreenOrientation 对象,含 angle (旋转角度)和 type (方向类型) |
示例:检测屏幕方向变化
screen.orientation.addEventListener('change', () => {
console.log('屏幕方向:', screen.orientation.type); // 如 "landscape-primary"
console.log('旋转角度:', screen.orientation.angle);
});
六、弹出框与用户交互
BOM 提供的原生弹出框功能简单但有诸多限制,需合理使用。
原生弹出框的局限性
- 样式不可定制:完全由浏览器控制,无法适配页面风格。
- 阻塞执行:弹出框显示时,JavaScript 暂停执行,影响用户体验。
- 无障碍性问题:部分屏幕阅读器支持有限,且无法添加辅助标签。
- 浏览器限制:可能被广告拦截器阻止,或在 iframe 中受限。
现代替代方案:使用自定义模态框(如基于 Bootstrap、React Modal 等库),结合 Promise
实现异步交互:
// 自定义 confirm 替代方案
function customConfirm(message) {
return new Promise((resolve) => {
// 创建模态框 DOM 并显示
const modal = createModal(message);
modal.confirmBtn.addEventListener('click', () => {
resolve(true);
modal.remove();
});
modal.cancelBtn.addEventListener('click', () => {
resolve(false);
modal.remove();
});
});
}
// 使用
customConfirm('确定要删除吗?').then((confirmed) => {
if (confirmed) {
// 执行删除操作
}
});
七、定时器与性能优化
定时器是 BOM 中处理异步任务的核心工具,但使用不当会导致性能问题。
定时器精度与替代方案
精度问题
- 浏览器的定时器延迟并非精确值,受 JavaScript 线程阻塞、浏览器休眠等影响。
setInterval
存在累积误差:若回调执行时间超过间隔,会导致回调叠加执行。
优化方案:使用 setTimeout
递归替代 setInterval
:
// 更可靠的重复执行
function repeatTask(fn, interval) {
let lastTime = Date.now();
function execute() {
const now = Date.now();
const delay = Math.max(0, interval - (now - lastTime)); // 修正延迟
lastTime = now;
fn();
timeoutId = setTimeout(execute, delay);
}
let timeoutId = setTimeout(execute, interval);
return () => clearTimeout(timeoutId); // 返回取消函数
}
动画专用定时器
requestAnimationFrame
是浏览器提供的动画优化 API,与屏幕刷新率同步(通常 60 次/秒):
function animate() {
// 更新动画状态
element.style.left = `${x}px`;
x++;
// 继续下一帧
if (x < 100) {
requestId = requestAnimationFrame(animate);
}
}
// 启动动画
let requestId = requestAnimationFrame(animate);
// 停止动画
// cancelAnimationFrame(requestId);
优势:
- 性能更好:浏览器后台标签页时会暂停,节省资源。
- 更流畅:与屏幕刷新同步,避免卡顿。
八、BOM 事件与页面生命周期
BOM 事件涵盖页面加载、可见性、尺寸变化等场景,合理监听可提升用户体验。
关键事件扩展
页面可见性(visibilitychange
)
监听页面是否处于用户可见状态(如切换标签页):
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// 页面不可见(暂停视频、减少请求)
video.pause();
} else {
// 页面可见(恢复播放、刷新数据)
video.play();
fetchNewData();
}
});
页面缓存与加载(pageshow
/pagehide
)
pageshow
:页面加载时触发(包括从缓存加载)。pagehide
:页面卸载时触发(包括进入缓存)。
window.addEventListener('pageshow', (event) => {
// 检测是否从缓存加载(如浏览器后退按钮)
if (event.persisted) {
console.log('页面从缓存加载');
// 刷新数据,避免显示旧内容
refreshData();
}
});
窗口大小变化防抖
resize
事件会频繁触发(如用户拖拽窗口),需用防抖优化:
function debounce(fn, delay = 100) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
window.addEventListener('resize', debounce(() => {
console.log('窗口尺寸稳定后执行:', window.innerWidth);
}));
九、BOM 安全限制与最佳实践
浏览器对 BOM 操作施加了严格限制,以保护用户安全和隐私。
核心安全限制
-
同源策略:不同源的窗口间无法访问彼此的
window
属性(除非通过postMessage
)。// 跨源通信示例(父窗口向子窗口发送消息) const iframe = document.querySelector('iframe'); iframe.contentWindow.postMessage('Hello', 'https://trusted-domain.com'); // 子窗口接收消息 window.addEventListener('message', (event) => { // 验证发送方来源 if (event.origin !== 'https://trusted-domain.com') return; console.log('收到消息:', event.data); // 回复消息 event.source.postMessage('Hi there', event.origin); });
-
弹窗限制:非用户交互触发的
window.open
会被阻止(如load
事件中)。// 正确做法:仅在用户点击时打开弹窗 document.getElementById('openBtn').addEventListener('click', () => { window.open('https://example.com'); // 允许执行 });
-
权限控制:地理定位、全屏等 API 需要用户明确授权,且可能被拒绝。
最佳实践
- 避免滥用 BOM 操作:如无必要,不修改窗口大小、位置,不依赖原生弹出框。
- 特性检测优先:使用
if ('feature' in object)
判断 API 支持,而非浏览器嗅探。 - 性能优化:对
resize
、scroll
等高频事件使用防抖/节流;动画优先用requestAnimationFrame
。 - 兼容处理:对旧浏览器(如 IE)使用前缀方法(
webkitRequestFullscreen
),或通过工具库(如 Modernizr)简化检测。 - 安全防护:跨源通信时验证
origin
;防御点击劫持(检测window.top
);避免在state
中存储敏感信息。