BOM浏览器对象模型
参考 JavaScript 高级程序设计 第四版
虽然 ECMAScript 把浏览器对象模型(BOM,Browser Object Model)描述为 JavaScript 的核心,但 6 实际上 BOM 是使用 JavaScript 开发 Web 应用程序的核心。
# 1. window 对象
BOM 的核心是 window 对象,表示浏览器的实例。
window 对象在浏览器中有两重身份,一个是 ECMAScript 中的 Global 对象,另一个就是浏览器窗口的 JavaScript 接口。
这意味着网页中定义的所有 对象、变量和函数都以 window 作为其 Global 对象,都可以访问其上定义的 parseInt()等全局方法.
# 1.1 Global 作用域
因为 window 对象被复用为 ECMAScript 的 Global 对象,所以通过 var 声明的所有全局变量和函 数都会变成 window 对象的属性和方法。比如:
var age = 29
var sayAge = () => alert(this.age)
alert(window.age) // 29
sayAge() // 29
window.sayAge() // 29
2
3
4
5
这里,变量 age 和函数 sayAge()被定义在全局作用域中,它们自动成为了 window 对象的成员。
因此,变量 age 可以通过 window.age 来访问,而函数 sayAge()也可以通过 window.sayAge()来访问。 因为 sayAge()存在于全局作用域,this.age 映射到 window.age,所以就可以显示正确的结果了。
# 1.2. 像素比
CSS 像素是 Web 开发中使用的统一像素单位。这个单位的背后其实是一个角度:0.0213°。如果屏 幕距离人眼是一臂长,则以这个角度计算的 CSS 像素大小约为 1/96 英寸。
这样定义像素大小是为了在 不同设备上统一标准。比如,低分辨率平板设备上 12 像素(CSS 像素)的文字应该与高清 4K 屏幕下 12 像素(CSS 像素)的文字具有相同大小。
这就带来了一个问题,不同像素密度的屏幕下就会有不同的缩放系数,以便把物理像素(屏幕实际的分辨率)转换为 CSS 像素(浏览器报告的虚拟分辨率)。
举个例子,手机屏幕的物理分辨率可能是 1920×1080,但因为其像素可能非常小,所以浏览器就需 要将其分辨率降为较低的逻辑分辨率,比如 640×320。
这个物理像素与 CSS 像素之间的转换比率由 window.devicePixelRatio 属性提供。
对于分辨率从 1920×1080 转换为 640×320 的设备,window. devicePixelRatio 的值就是 3。
这样一来,12 像素(CSS 像素)的文字实际上就会用 36 像素的物理 像素来显示。
window.devicePixelRatio 实际上与每英寸像素数(DPI,dots per inch)是对应的。
DPI 表示单 位像素密度,而 window.devicePixelRatio 表示物理像素与逻辑像素之间的缩放系数。
# 1.3 窗口大小
在不同浏览器中确定浏览器窗口大小没有想象中那么容易。所有现代浏览器都支持 4 个属性: innerWidth、innerHeight、outerWidth 和 outerHeight。
outerWidth 和 outerHeight 返回浏 览器窗口自身的大小(不管是在最外层 window 上使用,还是在窗格<frame>中使用)。
innerWidth 和 innerHeight 返回浏览器窗口中页面视口的大小(不包含浏览器边框和工具栏)。
document.documentElement.clientWidth 和 document.documentElement.clientHeight 返回页面视口的宽度和高度。
浏览器窗口自身的精确尺寸不好确定,但可以确定页面视口的大小,如下所示:
let pageWidth = window.innerWidth,
pageHeight = window.innerHeight;
if (typeof pageWidth != "number") { 11 if (document.compatMode == "CSS1Compat"){
pageWidth = document.documentElement.clientWidth;
pageHeight = document.documentElement.clientHeight;
} else {
pageWidth = document.body.clientWidth;
pageHeight = document.body.clientHeight;
}
}
2
3
4
5
6
7
8
9
10
这里,先将 pageWidth 和 pageHeight 的值分别设置为 window.innerWidth 和 window. innerHeight。
然后,检查 pageWidth 是不是一个数值,如果不是则通过 document.compatMode 来检查页面是否处于标准模式。
如果是,则使用 document.documentElement.clientWidth 和 document.documentElement.clientHeight;否则,就使用 document.body.clientWidth 和 document.body.clientHeight。
在移动设备上,window.innerWidth 和 window.innerHeight 返回视口的大小,也就是屏幕上 页面可视区域的大小。
Mobile Internet Explorer 支持这些属性,但在 document.documentElement. clientWidth 和 document.documentElement.clientHeight 中提供了相同的信息。
在放大或缩小 页面时,这些值也会相应变化。
在其他移动浏览器中,document.documentElement.clientWidth 和 document.documentElement. clientHeight 返回的布局视口的大小,即渲染页面的实际大小。
布局视口是相对于可见视口的概念, 可见视口只能显示整个页面的一小部分。
Mobile Internet Explorer 把布局视口的信息保存在 document.body.clientWidth 和 document.body.clientHeight 中。在放大或缩小页面时,这些 值也会相应变化。
因为桌面浏览器的差异,所以需要先确定用户是不是在使用移动设备,然后再决定使用哪个属性。
可以使用 **resizeTo()和 resizeBy()**方法调整窗口大小。这两个方法都接收两个参数,resizeTo() 接收新的宽度和高度值,而 resizeBy()接收宽度和高度各要缩放多少。下面看个例子:
// 缩放到100×100
window.resizeTo(100, 100)
// 缩放到200×150
window.resizeBy(100, 50)
// 缩放到300×300
window.resizeTo(300, 300)
2
3
4
5
6
与移动窗口的方法一样,缩放窗口的方法可能会被浏览器禁用,而且在某些浏览器中默认是禁用的。 同样,缩放窗口的方法只能应用到最上层的 window 对象。
# 1.4 导航与打开新窗口
window.open()方法可以用于导航到指定 URL,也可以用于打开新浏览器窗口。这个方法接收 4 个参数: 要加载的 URL、目标窗口、特性字符串和表示新窗口在浏览器历史记录中是否替代当前加载页 面的布尔值。通常,调用这个方法时只传前 3 个参数,最后一个参数只有在不打开新窗口时才会使用。
如果 window.open()的第二个参数是一个已经存在的窗口或窗格(frame)的名字,则会在对应的 窗口或窗格中打开 URL。下面是一个例子:
// 与<a href="http://www.wrox.com" target="topFrame"/>相同
window.open('http://www.wrox.com/', 'topFrame')
2
执行这行代码的结果就如同用户点击了一个 href 属性为"http://www.wrox.com",target 属 性为"topFrame"的链接。如果有一个窗口名叫"topFrame",则这个窗口就会打开这个 URL;否则就 会打开一个新窗口并将其命名为"topFrame"。第二个参数也可以是一个特殊的窗口名,比如_self、 _parent、_top 或_blank。
弹出窗口
如果 window.open()的第二个参数不是已有窗口,则会打开一个新窗口或标签页。第三个参数, 即特性字符串,用于指定新窗口的配置。如果没有传第三个参数,则新窗口(或标签页)会带有所有默 认的浏览器特性(工具栏、地址栏、状态栏等都是默认配置)。如果打开的不是新窗口,则忽略第三个 参数。
# 1.5 弹窗屏蔽程序
ew67g12gt
所有现代浏览器都内置了屏蔽弹窗的程序,因此大多数意料之外的弹窗都会被屏蔽。在浏览器屏蔽 弹窗时,可能会发生一些事。如果浏览器内置的弹窗屏蔽程序阻止了弹窗,那么 window.open()很可 能会返回 null。此时,只要检查这个方法的返回值就可以知道弹窗是否被屏蔽了,比如:
let blocked = false
try {
let wroxWin = window.open('http://wwww.baidu.com', '_blank')
if (wroxWin == null) {
blocked = true
}
} catch (error) {
blocked = trueew67g12gt
if (blocked) {
alert('The popup was blocked!')
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 1.6. 定时器
JavaScript 在浏览器中是单线程执行的,但允许使用定时器指定在某个时间之后或每隔一段时间就 执行相应的代码。setTimeout()用于指定在一定时间后执行某些代码,而 setInterval()用于指定 每隔一段时间执行某些代码。
# 1.7. 系统对话框
使用 alert()、confirm()和 prompt()方法,可以让浏览器调用系统对话框向用户显示消息。这 些对话框与浏览器中显示的网页无关,而且也不包含 HTML。它们的外观由操作系统或者浏览器决定, 无法使用 CSS 设置。此外,这些对话框都是同步的模态对话框,即在它们显示的时候,代码会停止执行, 在它们消失以后,代码才会恢复执行。
# 2. location 对象
location 是最有用的 BOM 对象之一,提供了当前窗口中加载文档的信息,以及通常的导航功能。 这个对象独特的地方在于,它既是 window 的属性,也是 document 的属性。
也就是说,window.location 和 document.location 指向同一个对象。location 对象不仅保存着当前加载文 档的信息,也保存着把 URL 解析为离散片段后能够通过属性访问的信息。这些解析后的属性在下表中 有详细说明(location 前缀是必需的)。
假设浏览器当前加载的 URL 是 http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q= javascript#contents,location 对象的内容如下表所示。
# 2.1 URLSearchParams
URLSearchParams 提供了一组标准 API 方法,通过它们可以检查和修改查询字符串。给 URLSearchParams 构造函数传入一个查询字符串,就可以创建一个实例。这个实例上暴露了 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('q')
alert(searchParams.toString()) // " num=10&page=3"
2
3
4
5
6
7
8
9
大多数支持 URLSearchParams 的浏览器也支持将 URLSearchParams 的实例用作可迭代对象:
let qs = '?q=javascript&num=10'
let searchParams = new URLSearchParams(qs)
for (let param of searchParams) {
console.log(param)
}
// ["q", "javascript"]
// ["num", "10"]
2
3
4
5
6
7
# 2.2 操作地址
可以通过修改 location 对象修改浏览器的地址。首先,最常见的是使用 assign()方法并传入一 个 URL,如下所示:
location.assign('http://www.wrox.com')
这行代码会立即启动导航到新 URL 的操作,同时在浏览器历史记录中增加一条记录。如果给 location.href 或 window.location 设置一个 URL,也会以同一个 URL 值调用 assign()方法。
比如,下面两行代码都会执行与显式调用 assign()一样的操作:
window.location = 'http://www.wrox.com'
location.href = 'http://www.wrox.com'
2
在这 3 种修改浏览器地址的方法中,设置 location.href是最常见的。
修改 location 对象的属性也会修改当前加载的页面。其中,hash、search、hostname、pathname 和 port 属性被设置为新值之后都会修改当前 URL,如下面的例子所示:
// 假设当前URL为http://www.wrox.com/WileyCDA/
// 把URL修改为http://www.wrox.com/WileyCDA/#section1
location.hash = '#section1'
// 把URL修改为http://www.wrox.com/WileyCDA/?q=javascript location.search = "?q=javascript";
// 把URL修改为http://www.somewhere.com/WileyCDA/ location.hostname = "www.somewhere.com";
// 把URL修改为http://www.somewhere.com/mydir/ location.pathname = "mydir";
// 把URL修改为http://www.somewhere.com:8080/WileyCDA/ location.port = 8080;
2
3
4
5
6
7
除了 hash 之外,只要修改 location 的一个属性,就会导致页面重新加载新 URL。
注意 修改 hash 的值会在浏览器历史中增加一条新记录。在早期的 IE 中,点击“后退” 和“前进”按钮不会更新 hash 属性,只有点击包含散列的 URL 才会更新 hash 的
在以前面提到的方式修改 URL 之后,浏览器历史记录中就会增加相应的记录。当用户单击“后退” 按钮时,就会导航到前一个页面。如果不希望增加历史记录,可以使用 replace()方法。这个方法接 收一个 URL 参数,但重新加载后不会增加历史记录。调用 replace()之后,用户不能回到前一页。比 如下面的例子:
<!DOCTYPE html>
<html>
<head>
<title>You won't be able to get back here</title>
</head>
<body>
<p>Enjoy this page for a second, because you won't be coming back here.</p>
<script>
setTimeout(() => location.replace('http://www.wrox.com/'), 1000)
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
浏览器加载这个页面 1 秒之后会重定向到 www.wrox.com。此时,“后退”按钮是禁用状态,即不能 返回这个示例页面,除非手动输入完整的 URL。
最后一个修改地址的方法是 reload(),它能重新加载当前显示的页面。调用 reload()而不传参 数,页面会以最有效的方式重新加载。也就是说,如果页面自上次请求以来没有修改过,浏览器可能会 从缓存中加载页面。如果想强制从服务器重新加载,可以像下面这样给 reload()传个 true:
location.reload(); // 重新加载,可能是从缓存加载 location.reload(true); // 重新加载,从服务器加载脚本中位于 reload()调用之后的代码可能执行也可能不执行,这取决于网络延迟和系统资源等因 素。为此,最好把 reload()作为最后一行代码。
# 3. screen 对象
window 的另一个属性 screen 对象,是为数不多的几个在编程中很少用的 JavaScript 对象。这个对 象中保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像 素高度。每个浏览器都会在 screen 对象上暴露不同的属性。下表总结了这些属性。
# 4. history 对象
history 对象表示当前窗口首次使用以来用户的导航历史记录。因为 history 是 window 的属性, 所以每个 window 都有自己的 history 对象。
出于安全考虑,这个对象不会暴露用户访问过的 URL, 但可以通过它在不知道实际 URL 的情况下前进和后退。
go()方法可以在用户历史记录中沿任何方向导航,可以前进也可以后退。这个方法只接收一个参数, 这个参数可以是一个整数,表示前进或后退多少步。负值表示在历史记录中后退(类似点击浏览器的“后 退”按钮),而正值表示在历史记录中前进(类似点击浏览器的“前进”按钮)。下面来看几个例子:
// 后退一页
history.go(-1)
// 前进一页
history.go(1)
// 前进两页
history.go(2)
2
3
4
5
6
在旧版本的一些浏览器中,go()方法的参数也可以是一个字符串,这种情况下浏览器会导航到历 史中包含该字符串的第一个位置。最接近的位置可能涉及后退,也可能涉及前进。如果历史记录中没有 匹配的项,则这个方法什么也不做,如下所示:
// 导航到最近的 wrox.com 页面
history.go('wrox.com')
// 导航到最近的 nczonline.net 页面
history.go('nczonline.net')
2
3
4
go()有两个简写方法:back()和 forward()。顾名思义,这两个方法模拟了浏览器的后退按钮和 前进按钮:
// 后退一页
history.back()
// 前进一页
history.forward()
2
3
4
history 对象还有一个 length 属性,表示历史记录中有多个条目。这个属性反映了历史记录的数 量,包括可以前进和后退的页面。对于窗口或标签页中加载的第一个页面,history.length 等于 1。 通过以下方法测试这个值,可以确定用户浏览器的起点是不是你的页面:
if (history.length == 1) {
// 这是用户窗口中的第一个页面
}
2
3
history 对象通常被用于创建“后退”和“前进”按钮,以及确定页面是不是用户历史记录中的第一条记录。
注意 如果页面 URL 发生变化,则会在历史记录中生成一个新条目。对于 2009 年以来发 布的主流浏览器,这包括改变 URL 的散列值(因此,把 location.hash 设置为一个新 值会在这些浏览器的历史记录中增加一条记录)。这个行为常被单页应用程序框架用来模 拟前进和后退,这样做是为了不会因导航而触发页面刷新。 Vue 的路由 hash 模式就是这个原理
# 4.1 历史状态管理
现代 Web 应用程序开发中最难的环节之一就是历史记录管理。用户每次点击都会触发页面刷新的 时代早已过去,“后退”和“前进”按钮对用户来说就代表“帮我切换一个状态”的历史也就随之结束 了。为解决这个问题,首先出现的是 hashchange 事件。HTML5 也为 history 对象增加了方便的状态管理特性。
hashchange 会在页面 URL 的散列变化时被触发,开发者可以在此时执行某些操作。而状态管理 API 则可以让开发者改变浏览器 URL 而不会加载新页面。为此,可以使用 history.pushState()方 法。这个方法接收 3 个参数:一个 state 对象、一个新状态的标题和一个(可选的)相对 URL。例如:
let stateObject = { foo: 'bar' }
history.pushState(stateObject, 'My title', 'baz.html')
2
pushState()方法执行后,状态信息就会被推到历史记录中,浏览器地址栏也会改变以反映新的相 对 URL。除了这些变化之外,即使 location.href 返回的是地址栏中的内容,浏览器页不会向服务器发送请求。第二个参数并未被当前实现所使用,因此既可以传一个空字符串也可以传一个短标题。第一 个参数应该包含正确初始化页面状态所必需的信息。为防止滥用,这个状态的对象大小是有限制的,通 常在 500KB~1MB 以内。
因为 pushState()会创建新的历史记录,所以也会相应地启用“后退”按钮。此时单击“后退” 按钮,就会触发 window 对象上的 popstate 事件。popstate 事件的事件对象有一个 state 属性,其 中包含通过 pushState()第一个参数传入的 state 对象:
window.addEventListener('popstate', (event) => {
let state = event.state
if (state) {
// 第一个页面加载时状态是null
processState(state)
}
})
2
3
4
5
6
7
基于这个状态,应该把页面重置为状态对象所表示的状态(因为浏览器不会自动为你做这些)。
记 住,页面初次加载时没有状态。因此点击“后退”按钮直到返回最初页面时,event.state 会为 null。
可以通过 history.state 获取当前的状态对象,也可以使用 replaceState()并传入与 pushState()同样的前两个参数来更新状态。更新状态不会创建新历史记录,只会覆盖当前状态:
history.replaceState({ newFoo: 'newBar' }, 'New title')
传给 pushState()和 replaceState()的 state 对象应该只包含可以被序列化的信息。因此, DOM 元素之类并不适合放到状态对象里保存。
使用 HTML5 状态管理时,要确保通过 pushState()创建的每个“假”URL 背后 都对应着服务器上一个真实的物理 URL。否则,单击“刷新”按钮会导致 404 错误。所有 单页应用程序(SPA,Single Page Application)框架都必须通过服务器或客户端的某些配 置解决这个问题。
# 5. navigator 对象
利用 navigator 对象可以获取浏览器的网络连接信息。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>js实现查看网络状态</title>
</head>
<body>
<div>
<h3>改变网络状态测试效果</h3>
<h1 id="p1">获取网络在线状态:</h1>
<h1 id="p2">获取网络具体状态:</h1>
<h1 id="p3">获取网络速度:</h1>
</div>
<script>
window.onload = function() {
getNetworkConnectionChange()
getNetworkOnLineChange()
}
// connection 的兼容性较低,谨慎使用
// 通过navigator.connection获取当前网络状态,可对connection进行监听,从而及时获取网络状态的变更
function getNetworkConnectionChange() {
// connection 的兼容性较低
const connection =
navigator.connection ||
navigator.webkitConnection ||
navigator.mozConnection
updateText('p2', '网络状态:' + connection.effectiveType)
updateText('p3', '网络下行速度:' + connection.downlink + 'MB/S')
// 对connection变更监听
connection.addEventListener('change', () => {
// connection.effectiveType返回的是具体的网络状态:4g/3g/2g
updateText('p2', '网络状态:' + connection.effectiveType)
updateText('p3', '网络下行速度:' + connection.downlink + 'MB/S')
})
}
// 通过navigator.online判断当前网络是否在线,对navigator进行监听,从而及时获取网络状态的变更
function getNetworkOnLineChange() {
updateText('p1', '您的网络是否在线:' + window.navigator.onLine)
// 对online网络在线变更监听
window.addEventListener('online', function() {
updateText('p1', '您的网络是否在线:' + window.navigator.onLine)
})
// 对offline断网变更监听
window.addEventListener('offline', function() {
updateText('p1', '您的网络是否在线:' + window.navigator.onLine)
})
}
function updateText(id, msg) {
document.getElementById(id).textContent = msg
console.log(msg)
}
</script>
</body>
</html>
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
# 6. 总结
浏览器对象模型(BOM,Browser Object Model)是以 window 对象为基础的,这个对象代表了浏 览器窗口和页面可见的区域。window 对象也被复用为 ECMAScript 的 Global 对象,因此所有全局变 量和函数都是它的属性,而且所有原生类型的构造函数和普通函数也都从一开始就存在于这个对象之上。
要引用其他 window 对象,可以使用几个不同的窗口指针。
通过 location 对象可以以编程方式操纵浏览器的导航系统。通过设置这个对象上的属性,可以改变浏览器 URL 中的某一部分或全部。
使用 replace()方法可以替换浏览器历史记录中当前显示的页面,并导航到新 URL。
navigator 对象提供关于浏览器的信息。提供的信息类型取决于浏览器,不过有些属性如 userAgent 是所有浏览器都支持的。
BOM 中的另外两个对象也提供了一些功能。screen 对象中保存着客户端显示器的信息。这些信息 通常用于评估浏览网站的设备信息。history 对象提供了操纵浏览器历史记录的能力,开发者可以确 定历史记录中包含多少个条目,并以编程方式实现在历史记录中导航,而且也可以修改历史记录。