跳到主要内容

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 中有很多对象都暴露在全局作用域中,比如 locationnavigator,因而它们也是 window 对象的属性。

窗口关系

topparentself 也是 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。实际上,selfwindow 就是同一个对象。之所以还要暴露 self,就是为了和 topparent 保持一致。

console.log(window.self === window); // 始终为 true

窗口位置与像素比

window 对象的位置可以通过不同的属性和方法来确定。现代浏览器提供了 screenLeftscreenTop 属性,用于表示窗口相对于屏幕左侧和顶部的位置,返回值的单位是 CSS 像素。

可以使用 moveTo()moveBy() 方法移动窗口。这两个方法都接收两个参数,其中 moveTo() 接收要移动到的新位置的绝对坐标 x 和 y;而 moveBy() 则接收相对当前位置在两个方向上移动的像素数。

moveTo() 语法
// x: 窗口左上角的新横坐标。
// y: 窗口左上角的新纵坐标。
window.moveTo(x, y);

// 将窗口移动到屏幕左上角
window.moveTo(0, 0);
moveBy() 语法
// dx: 窗口水平方向的移动距离。正值向右移动,负值向左移动。
// dy: 窗口垂直方向的移动距离。正值向下移动,负值向上移动
window.moveBy(dx, dy);

// 将窗口向右移动100像素,向下移动50像素
window.moveBy(100, 50);

// 将窗口向左移动200像素,向上移动100像素
window.moveBy(-200, -100);

在现代浏览器中,为了提高安全性和用户体验,对 moveTomoveBy 方法的使用进行了严格的限制。这些方法通常只能用于通过 window.open 打开的子窗口,而不能在普通的浏览器标签页中随意使用。

像素比

在Web开发中,了解设备像素比(Device Pixel Ratio, DPR)对于创建响应式设计和处理高分辨率显示屏(如Retina屏幕)非常重要。设备像素比的概念帮助开发者创建更清晰和适应不同设备分辨率的图像和界面。

设备像素比(Device Pixel Ratio, DPR)是物理像素与CSS像素之间的比率。它定义了一个设备显示屏上物理像素的数量与CSS像素数量的关系。

公式:DPR=物理像素数CSS像素数\text{公式:} DPR = \frac{\text{物理像素数}}{\text{CSS像素数}}

这个物理像素与 CSS 像素之间的转换比率由 window.devicePixelRatio 属性提供。

window.devicePixelRatio 实际上与每英寸像素数(DPI,dots per inch)是对应的。DPI 表示单位像素密度,而 window.devicePixelRatio 表示物理像素与逻辑像素之间的缩放系数。

窗口大小

在不同浏览器中确定浏览器窗口大小没有想象中那么容易。所有现代浏览器都支持 4 个属性: innerWidthinnerHeightouterWidthouterHeightouterWidthouterHeight 返回浏览器窗口自身的大小(不管是在最外层 window 上使用,还是在窗格<frame>中使用)。innerWidthinnerHeight 返回浏览器窗口中页面视口的大小(不包含浏览器边框和工具栏)。

document.documentElement.clientWidthdocument.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. scrollXwindow.pageYoffset / window.scrollY

可以使用 scroll()scrollTo()scrollBy() 方法滚动页面。这 3 个方法都接收表示相对视口距离的 x 和 y 坐标,这两个参数在前两个方法中表示要滚动到的坐标,在最后一个方法中表示滚动的距离。

scroll()scrollTo() 这两个方法功能相同,都是将页面滚动到指定的坐标位置。

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() 方法将其转换为字符串。

http://localhost:3000
alert

第二种对话框叫确认框,通过调用 confirm() 来显示。确认框跟警告框类似,都会向用户显示消息。但不同之处在于,确认框有两个按钮: 取消和确定。用户通过单击不同的按钮表明希望接下来执行什么操作。

http://localhost:3000
alert

用户单击了确认按钮还是取消按钮,可以判断 confirm() 方法的返回值: true 表示单击了确认按钮,false 表示单击了取消按钮。

if (confirm('你确定吗?')) {
// 用户点击了确认
} else {
// 用户点击取消
}

最后一种对话框是提示框,通过调用 prompt() 方法来显示。提示框的用途是提示用户输入消息。 除了确认和取消按钮,提示框还会显示一个文本框,让用户输入内容。prompt() 方法接收两个参数: 要显示给用户的文本,以及文本框的默认值(可以是空字符串)。

http://localhost:3000
alert

如果用户单击了确认按钮,则 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.locationdocument.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() 等方法,可以对查询字符串执行相应操作。

API 示例
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());

大多数支持 URLSearchParams 的浏览器也支持将 URLSearchParams 的实例用作可迭代对象:

let qs = '?name=javascript&post=location';
let searchParams = new URLSearchParams(qs);

for (let item of searchParams) {
console.log(item);
}
// [ 'name', 'javascript' ]
// [ 'post', 'location' ]

操作地址

可以通过修改 location 对象修改浏览器的地址。可以直接赋予 location.hrefwindow.location 新 URL 或者使用 assign() 方法并传入一个 URL。

location.href = 'https://google.com';
window.location = 'https://google.com';
location.assign('https://google.com');

在这 3 种修改浏览器地址的方法效果都是一样的,导航到新 URL 的操作,同时在浏览器历史记录中增加一条记录。前两个方法其实也是隐式使用 assign() 方法。

修改 location 对象的属性也会修改当前加载的页面。其中 hashsearchhostnamepathnameport 属性被设置为新值之后都会修改当前 URL。除了 hash 之外,只要修改 location 的一个属性,就会导致页面重新加载新 URL。

在以前面提到的方式修改 URL 之后,浏览器历史记录中就会增加相应的记录。当用户单击“后退” 按钮时,就会导航到前一个页面。如果不希望增加历史记录,可以使用 replace() 方法。这个方法接收一个 URL 参数,但重新加载后不会增加历史记录。调用 replace() 之后,用户不能回到前一页。

setTimeout(() => {
location.replace('https://google.com');
}, 1000);

最后一个修改地址的方法是 reload(),它能重新加载当前显示的页面。调用 reload() 而不传参数,页面会以最有效的方式重新加载。也就是说,如果页面自上次请求以来没有修改过,浏览器可能会从缓存中加载页面。如果想强制从服务器重新加载,可以给 reload() 传递参数 true:

location.reload(); // 重新加载,可能是从缓存加载
location.reload(true); // 重新加载,从服务器加载

navigator 是由 Netscape Navigator 2 最早引入浏览器的,现在已经成为客户端标识浏览器的标准。只要浏览器启用 JavaScript,navigator 对象就一定存在。但是与其他 BOM 对象一样,每个浏览器都支持自己的属性。

navigator 对象实现了 NavigatorID 、 NavigatorLanguage 、 NavigatorOnLine 、 NavigatorContentUtils 、 NavigatorStorage 、 NavigatorStorageUtils 、 NavigatorConcurrentHardware、NavigatorPlugins 和 NavigatorUserMedia 接口定义的属性和方法。

下表列出了这些接口定义的属性和方法:

属性/方法说明
activeVrDisplays返回数组,包含 ispresenting 属性为 true 的 VRDisplay 实例
appCodeName即使在非 Mozilla 浏览器中也会返回 "Mozilla"
appName浏览器全名
appVersion浏览器版本。通常与实际的浏览器版本不一致
battery返回暴露 Battery Status API 的 BatteryManager 对象
buildId浏览器的构建编号
connection返回暴露 Network Information API 的 NetworkInformation 对象
cookieEnabled返回布尔值,表示是否启用了 cookie
credentials返回暴露 Credentials Management API 的 CredentialsContainer 对象
deviceMemory返回单位为 GB 的设备内存容量
doNotTrack返回用户的 "不跟踪" (do-not-track) 设置
geolocation返回暴露 Geolocation API 的 Geolocation 对象
getVRDisplays()返回数组,包含可用的每个 VRDisplay 实例
getUserMedia()返回与可用媒体设备硬件关联的流
hardwareConcurrency返回设备的处理器核心数量
javaEnabled返回布尔值,表示浏览器是否启用了 Java
language返回浏览器的主语言
languages返回浏览器偏好的语言数组
locks返回暴露 Web Locks API 的 LockManager 对象
mediaCapabilities返回暴露 Media Capabilities API 的 MediaCapabilities 对象
mediaDevices返回可用的媒体设备
maxTouchPoints返回设备触摸屏支持的最大触点数
mimeTypes返回浏览器中注册的 MIME 类型数组
onLine返回布尔值,表示浏览器是否联网
oscpu返回浏览器运行的设备的操作系统和(或)CPU
permissions返回暴露 Permissions API 的 Permissions 对象
platform返回浏览器运行的系统平台
plugins返回浏览器安装的插件数组。在 IE 中,这个数组包含页面中所有 <embed> 元素
product返回产品名称(通常是 "Gecko")
productSub返回产品的额外信息(通常是 Gecko 的版本)
registerProtocolHandler()将一个网站注册为特定协议的处理程序
requestMediaKeySystemAccess()返回一个期约,解决为 MediaKeySystemAccess 对象
sendBeacon()异步传输一些小数据
serviceWorker返回用来与 ServiceWorker 实例交互的 ServiceWorkerContainer
share()返回当前平台的原生共享机制
storage返回暴露 Storage API 的 StorageManager 对象
userAgent返回浏览器的用户代理字符串
vendor返回浏览器的商名称
vendorSub返回浏览器厂商的更多信息
vibrate()触发设备振动
webdriver返回浏览器当前是否被自动化程序控制

navigator 对象的属性通常用于确定浏览器的类型。

检测插件

检测浏览器是否安装了某个插件是开发中常见的需求。通过 plugins 数组来确定。这个数组中的每一项都包含如下属性:

  • name:插件名称。
  • description:插件介绍。
  • filename:插件的文件名。
  • length:由当前插件处理的 MIME 类型数量。
let hasPlugin = function (name) {
name = name.toLowerCase();
for (let plugin of window.navigator.plugins) {
if (plugin.name.toLowerCase().indexOf(name) > -1) {
return true;
}
}
return false;
};
// 检测Flash
console.log(hasPlugin('Flash'));
// 检测QuickTime
console.log(hasPlugin('QuickTime'));

plugins 数组中的每个插件对象还有一个 MimeType 对象。每个 MimeType 对象有 4 个属性: description 描述 MIME 类型,enabledPlugin 是指向插件对象的指针,suffixes 是该 MIME 类型对应扩展名字符串,type 是完整的 MIME 类型字符串。

plugins 有一个 refresh() 方法,用于刷新 plugins 属性以反映新安装的插件。这个方法接收一个布尔值参数,表示刷新时是否重新加载页面。如果传入 true,则所有包含插件的页面都会重新加载。否则,只有 plugins 会更新,但页面不会重新加载。

注册处理程序

在现代浏览器中,navigator.registerProtocolHandler() 方法允许网站将其自身注册为处理特定协议(如 "mailto" 或 "web+myscheme")的默认应用程序。这对于在线 RSS 阅读器、电子邮件客户端等应用特别有用。

语法
// 第一参数:指定要处理的协议,例如 "mailto"、"web+rss" 或自定义协议。
// 第二参数:指定处理该协议的 URL。在这个 URL 中,%s 将被替换为协议的数据部分。
// 第三参数:指定应用程序的名称。

navigator.registerProtocolHandler(
'mailto', // 要处理的协议
'https://your-web-app.com/compose?to=%s', // 处理该协议的 URL
'Your Mail App', // 应用程序的名称
);
示例
if ('registerProtocolHandler' in navigator) {
navigator.registerProtocolHandler(
'mailto',
'https://example.com/compose?to=%s',
'Example Mail',
);
} else {
console.log('此浏览器不支持协议处理程序注册。');
}

screen 对象

window 的另一个属性 screen 对象,是为数不多的几个在编程中很少用的 JavaScript 对象。这个对象中保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度。每个浏览器都会在 screen 对象上暴露不同的属性。

属性说明
availHeight屏幕像素高度减去系统组件高度(只读)
availLeft没有被系统组件占用的屏幕的最左侧像素(只读)
availTop没有被系统组件占用的屏幕的最顶端像素(只读)
availWidth屏幕像素宽度减去系统组件宽度(只读)
colorDepth表示屏幕颜色的位数;多数系统是32(只读)
height屏幕像素高度
left当前屏幕左边的像素距离
pixelDepth屏幕的位深(只读)
top当前屏幕顶端的像素距离
width屏幕像素宽度
orientation返回 Screen Orientation API 中屏幕的朝向

history 对象

history 对象表示当前窗口首次使用以来用户的导航历史记录。因为 historywindow 的属性,所以每个 window 都有自己的 history 对象。出于安全考虑,这个对象不会暴露用户访问过的 URL,但可以通过它在不知道实际 URL 的情况下前进和后退。

导航

go() 方法可以在用户历史记录中沿任何方向导航,可以前进也可以后退。这个方法只接收一个参数,这个参数可以是一个整数,表示前进或后退多少步。负值表示在历史记录中后退(类似点击浏览器的“后 退”按钮),而正值表示在历史记录中前进(类似点击浏览器的“前进”按钮)。

// 后退一页
history.go(-1);

// 前进一页
history.go(1);

// 前进两页
history.go(2);

go() 有两个简写方法: back()forward()。顾名思义,这两个方法模拟了浏览器的后退按钮和前进按钮:

// 后退一页
history.back();

// 前进一页
history.forward();

history.length 表示历史记录中有多个条目。

console.log(history.length); // = 1 表示是启始页

if (history.length == 1) {
// 这是用户窗口中的第一个页面
}
备注

如果页面 URL 发生变化,则会在历史记录中生成一个新条目。对于 2009 年以来发布的主流浏览器,这包括改变 URL 的散列值(因此,把 location.hash 设置为一个新值会在这些浏览器的历史记录中增加一条记录)。这个行为常被单页应用程序框架用来模拟前进和后退,这样做是为了不会因导航而触发页面刷新。

历史状态管理

现代 Web 应用程序开发中最难的环节之一就是历史记录管理。用户每次点击都会触发页面刷新的 时代早已过去,“后退”和“前进”按钮对用户来说就代表“帮我切换一个状态”的历史也就随之结束了。为解决这个问题,首先出现的是 hashchange 事件。HTML5 也为 history 对象增加了方便的状态管理特性。

hashchange 会在页面 URL 的散列变化时被触发,开发者可以在此时执行某些操作。而状态管理 API 则可以让开发者改变浏览器 URL 而不会加载新页面。为此,可以使用 history.pushState() 方法。这个方法接收 3 个参数: 一个 state 对象、一个新状态的标题和一个(可选的)相对 URL。

let stateObject = { foo: 'bar' };
history.pushState(stateObject, 'My title', 'baz.html');

pushState() 方法执行后,状态信息就会被推到历史记录中,浏览器地址栏也会改变以反映新的相对 URL。除了这些变化之外,即使 location.href 返回的是地址栏中的内容,浏览器页不会向服务器发送请求。

因为 pushState() 会创建新的历史记录,所以也会相应地启用“后退”按钮。此时单击“后退” 按钮,就会触发 window 对象上的 popstate 事件。popstate 事件的事件对象有一个 state 属性,其中包含通过 pushState() 第一个参数传入的 state 对象:

window.addEventListener('popstate', event => {
let state = event.state;
if (state) {
// 第一个页面加载时状态是 null
processState(state);
}
});

基于这个状态,应该把页面重置为状态对象所表示的状态(因为浏览器不会自动为你做这些)。记住,页面初次加载时没有状态。因此点击“后退”按钮直到返回最初页面时,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)框架都必须通过服务器或客户端的某些配置解决这个问题。