精读《JavaScript 高级程序设计 第4版》第12章 BOM
BOM 指的是浏览器对象模型。它不是像DOM那样的官方标准(W3C或WHATWG),但它被所有现代浏览器所实现,提供了一组用于与浏览器窗口本身进行交互的对象。
核心思想:BOM的核心是 window
对象。在浏览器中,window
对象扮演着双重角色:
- 它是ECMAScript中的全局对象。所有在全局作用域中声明的变量和函数都会成为
window
对象的属性和方法。 - 它是浏览器窗口的JavaScript接口,提供了控制和访问浏览器窗口的能力。
12.1 Window对象
BOM的核心是window对象,表示浏览器的实例。window
对象扮演着双重角色,一个是ECMAScript中的Global对象,另一个就是浏览器窗口的JavaScript接口。这意味着网页中定义的所有对象、变量和函数都以window作为其Global对象,都可以访问其上定义的parseInt()等全局方法。
12.1.1 Global作用域
所有通过var声明的全局变量和函数都会成为window对象的属性:
var greeting = 'Hello World';
console.log(window.greeting); // 'Hello World'
现代注意点:使用 let
和 const
声明的全局变量不会成为 window
对象的属性。
let greetingLet = 'Hello';
const greetingConst = 'World';
console.log(window.greetingLet); // undefined
console.log(window.greetingConst); // undefined
12.1.2 窗口关系
top
:始终指向最顶层(最外层)窗口,即浏览器标签页本身。parent
:指向当前窗口的父窗口(如果当前窗口是<iframe>
,则parent
指向包含它的窗口)。self
:始终指向当前的window
对象(与window
是同义词)。
现代应用:在单页应用和微前端架构中,处理iframe
与主应用、或窗口与弹出窗口之间的通信时,这些属性至关重要。
12.1.3 窗口位置与像素比
screenLeft
/screenTop
:窗口相对于屏幕左侧和顶部的位置,返回值单位是CSS像素。screenX
/screenY
:同上,是标准属性。moveTo()
/moveBy()
:移动窗口。都接收两个参数,moveTo()
接收要移动到的新位置的绝对坐标;moveBy()
则接收相对当前位置在两个方向上移动的像素数。(依浏览器而定,可能被禁用)devicePixelRatio
:物理像素与CSS像素之间的转换比率。值为3时,12像素(CSS)的文字实际上会使用36像素的物理像素来显示。
12.1.4 窗口大小
innerWidth
/innerHeight
:视口(viewport) 的尺寸,即页面实际渲染区域的宽高(不包括浏览器UI、滚动条)。outerWidth
/outerHeight
:整个浏览器窗口的尺寸。resizeTo()
/resizeBy()
:调整窗口大小,接收两个参数,resizeTo()
接收新的宽高值,resizeBy()
接收宽高各要缩放多少。(可能被部分浏览器禁用)
12.1.5 视口位置
pageXoffset
/pageYoffset
:返回文档相对于视口的滚动距离scrollX
/scrollY
:同上scroll()
/scrollTo()/
scrollBy()
:滚动页面,都接收两个参数表示相对视口距离的x和y坐标,scrollTo()表示要滚动到的位置,scrollBy()表示要滚动的距离。也都接收一个ScrollToOptions字典,通过behavior属性告诉浏览器是否平滑滚动:
window.scrollTo({left: 100,top: 100,behavior: 'smooth'
})
12.1.6 导航与打开新窗口
window.open()
方法用于导航到一个新URL或打开一个新浏览器窗口。
用法:window.open(URL, target, features)
-
URL
:要加载的地址。target
:窗口目标,如_blank
,_self
,或一个窗口/框架名。features
:一个逗号分隔的设置字符串,用于控制新窗口的UI元素(如工具栏、状态栏、大小等)。
const popup = window.open('https://www.example.com', 'exampleWindow', 'width=400,height=300');
新创建窗口的window对象都有一个属性opener,指向打开它的窗口,可以通过将指定窗口对象的opener属性值置null,实现新窗口的运行在独立进程中。(一旦切断就无法恢复)
- 现代安全限制与最佳实践:
-
- 弹出窗口阻止程序:现代浏览器默认会阻止非用户触发的
window.open()
调用(例如在setTimeout
或异步回调中直接调用)。必须由用户手势(如点击)发起。 - 慎用:由于糟糕的用户体验和潜在的滥用(广告、钓鱼),应尽量避免使用弹出窗口。如果需要打开新标签页,通常使用
target="_blank"
的<a>
标签是更好的选择。 - 通信:通过
window.open()
返回的引用,可以实现父子窗口间的有限通信(如popup.close()
)。 popup.close()
只能用于window.open()
创建的窗口。
- 弹出窗口阻止程序:现代浏览器默认会阻止非用户触发的
12.1.7 定时器 (setTimeout
& setInterval
)
这是BOM中最常用、最稳定的API之一。
setTimeout(func, delay)
:在指定的毫秒数delay
后,将任务func
推入任务队列执行。返回一个ID,可用于取消。setInterval(func, delay)
:每隔指定的毫秒数delay
,重复将任务func
推入任务队列执行。返回一个ID,可用于取消。
现代注意点:
-
- 最小延迟:在非活动标签页中,现代浏览器会对定时器实施最小延迟(例如1秒)以节省资源和电池。
- 性能与替代方案:对于高频重复动画,
requestAnimationFrame
是比setInterval
更优的选择,因为它与浏览器的重绘周期同步,能保证动画的流畅性并节省电量。
// 使用 requestAnimationFrame 实现动画
function animate() {// ... 更新动画状态 ...element.style.left = `${newPosition}px`;requestAnimationFrame(animate);
}
animate();
-
- 取消定时器:务必使用
clearTimeout(timeoutId)
和clearInterval(intervalId)
来清理不再需要的定时器,防止内存泄漏。
- 取消定时器:务必使用
12.1.8 系统对话框
浏览器调用系统对话框向用户显示消息,这些对话框与网页无关,不包含HTML,外观由操作系统或浏览器决定,无法使用CSS设置。且这些对话框都是同步的静态对话框,显示时代码会停止执行,消失后代码才恢复执行。
alert()
:警告框,接收一个要显示给用户的字符串,该字符串会显示在对话框中,对话框只有一个"OK"(确定)按钮。confirm()
:确认框,与alert()不同的点在于有两个按钮:"Cancel"(取消)和"OK"(确定)。该方法返回Boolean值表示用户点击ok还是Cancel。- prompt():提示框,提示用户输入消息,除了ok和Cancel按钮,还会显示一个文本框,让用户输入内容。该方法接收两个参数,要显示给用户的文本和文本框的默认值(可空串),返回文本中的值或者取消时返回null。
12.2 location对象
它提供了当前窗口加载文档的信息和导航功能。它既是 window
的属性,也是 document
的属性 (window.location === document.location
)。
- 属性:包含了URL的各个部分。
-
location.href
:完整的URL。(如"http://www.wrox.com:80/WileyCDA/?q=javascript#contents")location.protocol
:协议(如 "http:")。location.host
:服务器名和端口号,(如"http://www.wrox.com:80" )。location.hostname
:服务器名,(如"www.wrox.com" )。location.port
:端口号,(如"80" )。location.pathname
:URL中的路径,(如"/WileyCDA/" )。location.search
:?
之后的查询字符串,(如 "?q=javascript")。location.hash
:#
之后的片段标识符,(如 "#contents")。
12.2.1 查询字符串
- location.search():返回从问号开始直到URL末尾的所有内容,需要自己编写函数进行二次处理。
- URLSearchParams():URLSearchParams提供了一组标准API方法,给该构造函数传入一个查询字符串,就可以创建一个实例。这个实例暴露了get()、set()和delete()等方法,可以对查询字符串执行相应操作。
let qs = "?q=javascript&num=10";
let searchParams = new URLSearchParams(qs);
alert(searchParams.toString()); //" q=javascript&num=10"
searchParams.has("num"); //true
searchParams.get("num"); //10
searchParams.set("page", "3");
alert(searchParams.toString()); //" q=javascript&num=10&page=3"
searchParams.delete("num");
alert(searchParams.toString()); //" q=javascrip&page=3"
12.2.2 操作地址
location.assign("https://...")
:导航到新URL,在历史记录中生成一条记录。location.replace("https://...")
:导航到新URL,替换当前历史记录,用户不能点击“返回”按钮回到前一个页面。location.reload()
:重新加载当前页面。传true
会强制从服务器重新加载(绕过缓存)。- 修改location对象属性也会修改当前加载的页面,使得页面重新加载新URL(例如:
location.hostname="www.baidu.com";
)
现代应用:
- 单页应用:在React, Vue等SPA框架中,我们通常使用客户端路由库(如React Router, Vue Router)。这些库的核心就是通过监听
location.hash
(Hash模式)或使用History API(History模式)来改变URL,而不触发完整的页面刷新。 - URLSearchParams:现代浏览器提供了
URLSearchParams
API,用于方便地解析和操作查询字符串。
const params = new URLSearchParams(location.search);
const id = params.get('id'); // 获取查询参数 'id' 的值
params.set('page', '2'); // 设置参数
console.log(params.toString()); // 输出序列化后的字符串
12.3 navigator对象
它提供了关于浏览器和操作系统的大量信息。其对象属性通常用来确定浏览器的类型。
经典属性:
-
navigator.userAgent
:浏览器的用户代理字符串。注意:由于历史原因,这个字符串非常混乱且容易被篡改,不推荐用于精确的浏览器检测。
-
navigator.platform
:操作系统平台。navigator.languages
:返回浏览器的主语言
现代属性与方法(越来越重要):
-
navigator.onLine
:返回布尔值,表示浏览器是否联网。可以监听online
和offline
事件。
-
navigator.clipboard
:剪贴板API。提供了安全、异步的读写剪贴板内容的能力。
// 写入文本到剪贴板
navigator.clipboard.writeText('Hello, Clipboard!').then(() => {console.log('Text copied to clipboard');
});
-
navigator.mediaDevices
:媒体设备API。用于访问摄像头和麦克风。
// 获取用户媒体(摄像头)
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {videoElement.srcObject = stream;});
-
navigator.serviceWorker
:Service Worker API。用于创建可编程的网络代理,是实现PWA(渐进式Web应用)、离线体验和后台同步的核心。navigator.geolocation
:地理定位API。用于获取用户设备的地理位置。
12.4 history 对象
它代表了用户的导航历史记录。出于安全考虑,不允许脚本直接访问具体的URL,但可以对其进行操作。
- 传统方法:
-
history.go(n)
:在历史记录中导航n步(正数前进,负数后退)。history.back()
:后退一页。history.forward()
:前进一页。history.length
:历史记录栈中的条目数。
现代核心:History API
这是现代单页应用的基石。它允许我们更新URL而不触发页面刷新,并管理应用状态。
-
history.pushState(state, title, url)
:向历史记录栈添加一条记录。URL改变,但页面不刷新。state
是一个与新历史记录条目关联的状态对象。history.replaceState(state, title, url)
:替换当前历史记录条目。URL改变,但页面不刷新。
示例(SPA路由):
// 当用户点击一个“关于我们”的链接时
function navigateToAbout() {// 1. 使用 pushState 改变URLhistory.pushState({ page: 'about' }, 'About Us', '/about');// 2. 然后,通过你自己的路由逻辑,动态地更新页面内容(例如,渲染About组件)renderAboutPage();
}// 监听 popstate 事件,当用户点击前进/后退按钮时触发
window.addEventListener('popstate', (event) => {// 根据 event.state 中的信息,渲染对应的页面if (event.state && event.state.page === 'about') {renderAboutPage();} else {renderHomePage();}
});