BOM
BOM
全称为浏览器对象模型(Browser Object Model),是一种让 JavaScript 与浏览器进行交互的编程接口。通过BOM,开发者可以访问和操作浏览器窗口、文档、历史记录、位置以及其他浏览器特性,从而实现动态网页和复杂的用户交互。
window 对象
BOM
的核心是 window
对象,表示浏览器的实例。window
对象在浏览器中有两重身份,一个是 ECMAScript 中的 Global 对象,另一个就是浏览器窗口的 JavaScript 接口。这意味着网页中定义的所有对象、变量和函数都以 window
作为其 Global 对象,都可以访问其上定义的 parseInt()
等全局方法。
Global 作用域
因为 window
对象被复用为 ECMAScript 的 Global 对象,所以通过 var
声明的所有全局变量和函 数都会变成 window
对象的属性和方法。
var age = 18;
var sayAge = () => console.log(age);
console.log(window.age); // 18
window.sayAge(); // 18
记住,JavaScript 中有很多对象都 暴露在全局作用域中,比如 location
和 navigator
,因而它们也是 window
对象的属性。
窗口关系
如 top
、parent
和 self
也是 window
对象的属性。理解这些属性的作用和关系有助于更好地操控浏览器窗口和嵌套框架(iframe)。以下是对这些属性的详细解释:
window.top
对象始终指向最外层的窗口,即浏览器窗口本身。无论有多少层嵌套(如iframe嵌套),top
始终指向最顶层的窗口
console.log(window.top === window); // 对于最顶层窗口,结果为 true
window.parent
对象始终指向当前窗口的父窗口。如果当前窗口是最顶层窗口,则 window.parent
等于 window.top
(也等于 window
)。
console.log(window.parent === window); // 对于最顶层窗口,结果为 true
还有一个 self
对象,它是终极 window
属性,始终会指向 window
。实际上,self
和 window
就是同一个对象。之所以还要暴露 self
,就是为了和 top
、parent
保持一致。
console.log(window.self === window); // 始终为 true
窗口位置与像素比
window
对象的位置可以通过不同的属性和方法来确定。现代浏览器提供了 screenLeft
和 screenTop
属性,用于表示窗口相对于屏幕左侧和顶部的位置,返回值的单位是 CSS 像素。
可以使用 moveTo()
和 moveBy()
方法移动窗口。这两个方法都接收两个参数,其中 moveTo()
接收要移动到的新位置的绝对坐标 x 和 y;而 moveBy()
则接收相对当前位置在两个方向上移动的像素数。
// x: 窗口左上角的新横坐标。
// y: 窗口左上角的新纵坐标。
window.moveTo(x, y);
// 将窗口移动到屏幕左上角
window.moveTo(0, 0);
// dx: 窗口水平方向的移动距离。正值向右移动,负值向左移动。
// dy: 窗口垂直方向的移动距离。正值向下移动,负值向上移动
window.moveBy(dx, dy);
// 将窗口向右移动100像素,向下移动50像素
window.moveBy(100, 50);
// 将窗口向左移动200像素,向上移动100像素
window.moveBy(-200, -100);
在现代浏览器中,为了提高安全性和用户体验,对 moveTo
和 moveBy
方法的使用进行了严格的限制。这些方法通常只能用于通过 window.open
打开的子窗口,而不能在普通的浏览器标签页中随意使用。
像素比
在Web开发中,了解设备像素比(Device Pixel Ratio, DPR)对于创建响应式设计和处理高分辨率显示屏(如Retina屏幕)非常重要。设备像素比的概念帮助开发者创建更清晰和适应不同设备分辨率的图像和界面。
设备像素比(Device Pixel Ratio, DPR)是物理像素与CSS像素之间的比率。它定义了一个设备显示屏上物理像素的数量与CSS像素数量的关系。
这个物理像素与 CSS 像素之间的转换比率由 window.devicePixelRatio
属性提供。
window.devicePixelRatio
实际上与每英寸像素数(DPI,dots per inch)是对应的。DPI 表示单位像素密度,而 window.devicePixelRatio
表示物理像素与逻辑像素之间的缩放系数。
窗口大小
在不同浏览器中确定浏览器窗口大小没有想象中那么容易。所有现代浏览器都支持 4 个属性: innerWidth
、innerHeight
、outerWidth
和 outerHeight
。outerWidth
和 outerHeight
返回浏览器窗口自身的大小(不管是在最外层 window 上使用,还是在窗格<frame>
中使用)。innerWidth
和 innerHeight
返回浏览器窗口中页面视口的大小(不包含浏览器边框和工具栏)。
document.documentElement.clientWidth
和 document.documentElement.clientHeight
返回页面视口的宽度和高度。
可以使用 resizeTo()
和 resizeBy()
方法调整窗口大小。这两个方法都接收两个参数,resizeTo()
接收新的宽度和高度值,而 resizeBy()
接收宽度和高度各要缩放多少。
// 缩放到100×100
window.resizeTo(100, 100);
// 缩放到200×150
window.resizeBy(100, 50);
// 缩放到300×300
window.resizeTo(300, 300);
与移动窗口的方法一样,缩放窗口的方法只能应用 window.open()
打开的子窗口。
视口位置
浏览器窗口尺寸通常无法满足完整显示整个页面,为此用户可以通过滚动在有限的视口中查看文档。度量文档相对于视口滚动距离的属性有两对,返回相等的值: window.pageXoffset
/ window. scrollX
和 window.pageYoffset
/ window.scrollY
。
可以使用 scroll()
、scrollTo()
和 scrollBy()
方法滚动页面。这 3 个方法都接收表示相对视口距离的 x 和 y 坐标,这两个参数在前两个方法中表示要滚动到的坐标,在最后一个方法中表示滚动的距离。
scroll()
和 scrollTo()
这两个方法功能相同,都是将页面滚动到指定的坐标位置。
//x: 要滚动到的横坐标。
// y: 要滚动到的纵坐标。
window.scroll(x, y);
window.scrollTo(x, y);
scrollBy()
此方法根据当前的滚动位置,滚动指定的距离。
// dx: 水平滚动的距离(正值向右,负值向左)。
// dy: 垂直滚动的距离(正值向下,负值向上)。
window.scrollBy(dx, dy);
这几个方法也都接收一个 ScrollToOptions
字典,除了提供偏移值,还可以通过 behavior
属性告诉浏览器是否平滑滚动。
// 正常滚动
window.scrollTo({
left: 100,
top: 100,
behavior: 'auto',
});
// 平滑滚动
window.scrollTo({
left: 100,
top: 100,
behavior: 'smooth',
});
导航与打开新窗口
window.open()
方法可以用于导航到指定 URL,也可以用于打开新浏览器窗口。这个方法接收 4 个参数: 要加载的 URL、目标窗口、特性字符串和表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值。通常,调用这个方法时只传前 3 个参数,最后一个参数只有在不打开新窗口时才会使用。
window.open()
方法允许你打开一个新的浏览器窗口或标签页,或者在现有的窗口或标签页中加载一个 URL。如果 window.open()
的第二个参数是一个已经存在的窗口或框架(frame)的名称,则会在对应的窗口或框架中再次打开指定的URL,这相当于刷新该窗口或框架。
<a href="http://www.example.com" target="topFrame"
>跳转 http://www.example.com</a
>
<script>
// 会在相同窗口下重新打开(刷新)
window.open('http://www.example.com', 'topFrame');
</script>
第二个参数也可以是一个特殊的窗口名,比如 _self
、 _parent
、_top
或 _blank
。
_self
:在当前窗口或标签页中打开URL。_parent
:在当前框架的父框架中打开URL。如果没有父框架,则行为与_self
相同。_top
:在最顶层的框架(即整个窗口或标签页)中打开URL,取消所有框架。_blank
:在新窗口或新标签页中打开URL。
弹出窗口
如果 window.open()
的第二个参数不是已有窗口,则会打开一个新窗口或标签页。第三个参数,即特性字符串,用于指定新窗口的配置。如果没有传第三个参数,则新窗口(或标签页)会带有所有默认的浏览器特性(工具栏、地址栏、状态栏等都是默认配置)。如果打开的不是新窗口,则忽略第三个参数。
特性字符串是一个逗号分隔的设置字符串,用于指定新窗口包含的特性。下表列出了一些选项。
设置 | 值 | 说 明 |
---|---|---|
fullscreen | "yes" 或 "no" | 表示新窗口是否最大化。仅限IE支持 |
location | "yes" 或 "no" | 表示是否显示地址栏。不同浏览器的默认值也不一样。在设置为"no"时,地址栏可能隐藏或禁用(取决于浏览器) |
menubar | "yes" 或 "no" | 表示是否显示菜单栏。默认值为"no" |
resizable | "yes" 或 "no" | 表示是否可以拖动改变新窗口大小。默认值为"no" |
scrollbars | "yes" 或 "no" | 表示是否可以在内容过长时滚动。默认值为"no" |
status | "yes" 或 "no" | 表示是否显示状态栏。不同浏览器的默认值也不一样 |
toolbar | "yes" 或 "no" | 表示是否显示工具栏。默认值为"no" |
left | 数值 | 新窗口的x轴坐标。这个值不能是负值 |
top | 数值 | 新窗口的y轴坐标。这个值不能是负值 |
width | 数值 | 新窗口的宽度。这个值不能小于100 |
height | 数值 | 新窗口高度。这个值不能小于100 |
这些设置需要以逗号分隔的名值对形式出现,其中名值对以等号连接(特性字符串中不能包含空格)。
const newWindow = window.open(
'http://www.example.com/',
'windowName',
'height=400,width=400,top=10,left=10,resizable=yes',
);
window.open()
方法返回一个对新建窗口的引用。这个对象与普通 window 对象没有区别,只是为控制新窗口提供了方便。例如,某些浏览器默认不允许缩放或移动主窗口,但可能允许缩放或移动通过 window.open()
创建的窗口。跟使用任何 window
对象一样,可以使用这个对象操纵新打开的窗口。
// 缩放
newWindow.resizeTo(500, 500);
// 关闭窗口
newWindow.close();
新创建窗口的 window
对象有一个属性 opener
,指向打开它的窗口。这个属性只在弹出窗口的最上层 window
对象(top)有定义,是指向调用 window.open()
打开它的窗口或窗格的指针。
let newWindow = window.open(
'http://www.example.com/',
'newWindow',
'height=400,width=400,top=10,left=10,resizable=yes',
);
console.log(newWindow.opener === window); // true
在某些浏览器中,每个标签页会运行在独立的进程中。如果一个标签页打开了另一个,而 window
对象需要跟另一个标签页通信,那么标签便不能运行在独立的进程中。在这些浏览器中,可以将新打开 的标签页的 opener
属性设置为 null
,表示新打开的标签页可以运行在独立的进程中。
newWindow.opener = null;
把 opener
设置为 null
表示新打开的标签页不需要与打开它的标签页通信,因此可以在独立进程中运行。这个连接一旦切断,就无法恢复了。
安全限制
弹出窗口有段时间被在线广告用滥了。很多在线广告会把弹出窗口伪装成系统对话框,诱导用户点击。因为长得像系统对话框,所以用户很难分清这些弹窗的来源。为了让用户能够区分清楚,浏览器开始对弹窗施加限制。
现在,浏览器会在用户操作下才允许创建弹窗。在网页加载过程中调用 window.open()
没有效果,而且还可能导致向用户显示错误。弹窗通常可能在鼠标点击或按下键盘中某个键的情况下才能打开。
弹窗屏蔽程序
所有现代浏览器都内置了屏蔽弹窗的程序,因此大多数意料之外 的弹窗都会被屏蔽。在浏览器屏蔽弹窗时,可能会发生一些事。如果浏览器内置的弹窗屏蔽程序阻止了弹窗,那么 window.open()
很可能会返回 null
。此时,只要检查这个方法的返回值就可以知道弹窗是否被屏蔽了。
let newWindow = window.open('http://www.example.com', '_blank');
if (newWindow == null) {
console.log('弹窗被屏蔽了');
}
在浏览器扩展或其他程序屏蔽弹窗时,window.open()
通常会抛出错误。因此要准确检测弹窗是否被屏蔽,除了检测 window.open()
的返回值,还要把它用 try/catch
包装起来,像这样:
let blocked = false;
try {
let newWindow = window.open('http://www.example.com', '_blank');
if (newWindow == null) {
blocked = true;
}
} catch (e) {
blocked = true;
}
if (blocked) {
console.log('弹窗被屏蔽了');
}
定时器
JavaScript 在浏览器中是单线程执行的,但允许使用定时器指定在某个时间之后或每隔一段时间就执行相应的代码。setTimeout()
用于指定在一定时间后执行某些代码,而 setInterval()
用于指定每隔一段时间执行某些代码。
setTimeout()
方法通常接收两个参数:要执行的代码和在执行回调函数前等待的时间(毫秒)。第一个参数可以是包含 JavaScript 代码的字符串(类似于传给 eval()
的字符串)或者一个函数。
// 在 1 秒后打印 hello world!
setTimeout(() => console.log('hello world!'), 1000);
第二个参数是要等待的毫秒数,而不是要执行代码的确切时间。JavaScript 是单线程的,所以每次只能执行一段代码。为了调度不同代码的执行,JavaScript 维护了一个任务队列。其中的任务会按照添加到队列的先后顺序执行。setTimeout()
的第二个参数只是告诉 JavaScript 引擎在指定的毫秒数过后把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待前面的任务执行完才能执行。
调用 setTimeout()
时,会返回一个表示该超时排期的数值 ID。这个超时 ID 是被排期执行代码的唯一标识符,可用于取消该任务。要取消等待中的排期任务,可以调用 clearTimeout()
方法并传入超时ID。
// 设置超时任务
let timeoutId = setTimeout(() => console.log('hello world!'), 1000);
// 取消超时任务
clearTimeout(timeoutId);
所有超时执行的代码(函数)都会在全局作用域中的一个匿名函数中运行,因此函数中的 this
值在非严格模式下始终指向 window
,而在严格模式下是 undefined。如果给 setTimeout()
提供了一个箭头函数,那么 this
会保留为定义它时所在的执行上下文作用域。
setInterval()
与 setTimeout()
的使用方法类似,只不过指定的任务会每隔指定时间就执行一次,直到取消循环定时或者页面卸载。setInterval()
同样可以接收两个参数: 要执行的代码(字符 串或函数),以及把下一次执行定时代码的任务添加到队列要等待的时间(毫秒)。下面是一个例子:
// 每隔一秒打印一次
const id = setInterval(() => console.log('hello world!'), 1000);
// 关闭定时器
clearInterval(id);
setIntervale()
在实践中很少会在生产环境下使用,因为一个任务结束和下一个任务开始之间的时间间隔是无法保证的,有些循环定时任务可能会因此而被跳过。而像前面这个例子中一样使用 setTimeout()
则能确保不会出现这种情况。一般来说,最好不要使用 setInterval()
。
系统对话框
使用 alert()
、confirm()
和 prompt()
方法,可以让浏览器调用系统对话框向用户显示消息。这些对话框与浏览器中显示的网页无关,而且也不包含 HTML。它们的外观由操作系统或者浏览器决定,无法使用 CSS 设置。此外,这些对话框都是同步的模态对话框,即在它们显示的时候,代码会停止执行,在它们消失以后,代码才会恢复执行。
第一种对话框叫警告框,alert()
只接收一个参数。调用 alert()
时,传入的字符串会显示在一个系统对话框中。对话框只有一个确定按钮。如果传给 alert()
的参数不是一个原始字符串,则会调用这个值的 toString()
方法将其转换为字符串。
第二种对话框叫确认框,通过调用 confirm()
来显示。确认框跟警告框类似,都会向用户显示消息。但不同之处在于,确认框有两个按钮: 取消和确定。用户通过单击不同的按钮表明希望接下来执行什么操作。
用户单击了确认按钮还是取消按钮,可以判断 confirm()
方法的返回值: true 表示单击了确认按钮,false 表示单击了取消按钮。
if (confirm('你确定吗?')) {
// 用户点击了确认
} else {
// 用户点击取消
}
最后一种对话框是提示框,通过调用 prompt()
方法来显示。提示框的用途是提示用户输入消息。 除了确认和取消按钮,提示框还会显示一个文本框,让用户输入内容。prompt()
方法接收两个参数: 要显示给用户的文本,以及文本框的默认值(可以是空字符串)。
如果用户单击了确认按钮,则 prompt()
会返回文本框中的值。如果用户单击了取消按钮,或者对话框被关闭,则 prompt()
会返回 null
。
let result = prompt('你叫 什么名字 ', '');
if (result !== null) {
console.log('Welcome, ' + result);
}
这些系统对话框可以向用户显示消息、确认操作和获取输入。由于不需要 HTML 和 CSS,所以系 统对话框是 Web 应用程序最简单快捷的沟通手段。
JavaScript 还可以显示另外两种对话框: find()
和 print()
。这两种对话框都是异步显示的,即控制权会立即返回给脚本。用户在浏览器菜单上选择“查找”(find)和“打印”(print)时显示的就是这两种对话框。通过在 window
对象上调用 find()
和 print()
可以显示它们,比如:
不同浏览器对 window.find()
方法的支持和实现可能有所不同。浏览器可能不支持调用方法主动唤出,可以通过快捷键 Ctrl + F
来唤出。
location 对象
location
是最有用的 BOM
对象之一,提供了当前窗口中加载文档的信息,以及通常的导航功能。这个对象独特的地方在于,它既是 window
的属性,也是 document
的属性。也就是说, window.location
和 document.location
指向同一个对象。location
对象不仅保存着当前加载文档的信息,也保存着把 URL 解析为离散片段后能够通过属性访问的信息。
以 https://mofan.password@nonfan.github.io/javascript-docs/bom/?q=location#location-对象
为例,查看 location 对象的内容:
属性 | 值 | 说明 |
---|---|---|
location.hash | "#location-%E5%AF%B9%E8%B1%A1" | URL 散列值(井号后跟零或多个字符),如果没有则为空字符串 |
location.host | "nonfan.github.io:80" | 服务器名及端口号 |
location.hostname | "nonfan.github.io" | 服务器名 |
location.href | "https://nonfan.github.io/javascript-docs/bom/?q=location#location-%E5%AF%B9%E8%B1%A1" | 当前加载页面的完整 URL。location 的 toString() 方法返回这个值 |
location.pathname | "/javascript-docs/bom/" | URL 中的路径和(或)文件名 |
location.port | "80" | 请求的端口。如果 URL 中没有端口,则返回空字符串 |
location.protocol | "https:" | 页面使用的协议。通常是 "http:" 或 "https:" |
location.search | "?q=location" | URL 的查询字符串。这个字符串以问号开头 |
location.username | "mofan" | 域名前指定的用户名 |
location.password | "password" | 域名前指定的密码 |
location.origin | "https://nonfan.github.io" | URL 的源地址。只读 |
查询字符串
location
的多数信息都可以通过上面的属性获取。但是 URL 中的查询字符串并不容易使用。虽然 location.search
返回了从问号开始直到 URL 末尾的所有内容,但没有办法逐个访问每个查询参数。
let getQueryStringArgs = function () {
// 取得没有开头问号的查询字符串
let qs = location.search.length > 0 ? location.search.substring(1) : '';
//保存数据的对象;
let args = {};
// 把每个参数添加到 args 对象
for (let item of qs.split('&').map(kv => kv.split('='))) {
// 必须进行解码,默认得到是字符串是编码后的
let name = decodeURIComponent(item[0]);
let value = decodeURIComponent(item[1]);
if (name.length) {
args[name] = value;
}
}
return args;
};
// 地址:https://nonfan.github.io/javascript-docs/bom/?q=location#location-对象
let args = getQueryStringArgs();
console.log(args); // {q: 'location'}
URLSearchParams 提供了一组标准 API 方法,通过它们可以检查和修改查询字符串。给 URLSearchParams 构造函数传入一个查询字符串,就可以创建一个实例。这个实例上暴露了 get()
、 set()
和 delete()
等方法,可以对查询字符串执行相应操作。
let qs = '?name=javascript&post=location';
let searchParams = new URLSearchParams(qs);
console.log(searchParams.toString());
searchParams.has('name'); // name=javascript&post=locationlet qs = '?name=javascript&post=location';
let searchParams = new URLSearchParams(qs);
console.log(searchParams.toString()); // name=javascript&post=location
console.log(searchParams.has('name')); // true
console.log(searchParams.get('name')); // javascript
searchParams.set('page', '1');
console.log(searchParams.toString()); // name=javascript&post=location&page=1
searchParams.delete('name');
console.log(searchParams.toString()); // post=location&page=1s
searchParams.get('name');
searchParams.set('page', '1');
console.log(searchParams.toString());
searchParams.delete('name');
console.log(searchParams.toString());