阅读 182

可能不是史上最全但很实用的BOM API详解-1

什么是 BOM?

BOM 是 browser object model 的缩写,中文意思是浏览器对象模型,提供了一系列操作浏览器的 API。与 ECMAScript 和 DOM 共同组成了 JavaScript。
在 Web 早期,BOM 没有标准规范,完全是由不同的浏览器厂商来实现的,所以不同的浏览器之间可能会存在差异。在 HTML5 规范中加入了 BOM 规范,弥补了这个问题。

BOM 能做什么?

打开新的标签页。

<button id="btn">打开百度首页</button>

<script>
  const btn = document.getElementById("btn");
  btn.addEventListener("click", () => window.open("https://www.baidu.com"));
</script>
复制代码

关闭当前标签页。

<button id="btn">关闭页面</button>

<script>
	const btn = document.getElementById('btn')
	btn.addEventListener('click', () => window.close())
</script>
复制代码

弹出新的浏览器窗口。

移动窗口。
缩放窗口。
控制用户浏览记录。
获取浏览器详细信息。
获取用户显示器分辨率详细信息。
其他更多功能。

BOM 包含什么?

JavaScript 最初是为 Web 浏览器创建的,但是后来它发展成了多用途和多平台的语言。任何能够提供 JavaScript 运行时的平台都可以称为 JavaScript 主机环境(host)。
目前主流的 JavaScript 平台有 Web 浏览器和可以运行服务端的 Node.js。
任何平台都需要有一个全局对象(Global)/根对象(Root)/宿主对象(Host),用于扩展除了 ECMAScript 语言自身的对象和函数外自己提供的对象和函数。
浏览器环境下的全局对象是 window,它扩展了 DOM 和 BOM。
window 除了具有全局对象含意外,还拥有另一个含意,是“浏览器窗口”。
它的组成如下:
image.png
从图中可以看到,BOM 主要包含了 Navigator、Screen、Location、Frames、History 和 XMLHttpRequest 6 个对象。下面逐个讲述它们的用途。

Navigator

Navigator 对象记录了访问者信息。
获取常用属性的示例代码如下:

<div id="info"></div>

<script>
  const info = document.getElementById("info")
  info.innerHTML += `浏览器应用程序名称: ${navigator.appName} </br>`
  info.innerHTML += `浏览器应用程序代码: ${navigator.appCodeName} </br>`
  info.innerHTML += `浏览器引擎: ${navigator.product} </br>`
  info.innerHTML += `浏览器版本: ${navigator.appVersion} </br>`
  info.innerHTML += `浏览器代理: ${navigator.userAgent} </br>`
  info.innerHTML += `浏览器平台: ${navigator.platform} </br>`
  info.innerHTML += `浏览器语言: ${navigator.language} </br>`
  info.innerHTML += `浏览器是否在线: ${navigator.onLine} </br>`
  info.innerHTML += `是否启用 cookie: ${navigator.cookieEnabled} </br>`
  info.innerHTML += `处理器核心数: ${navigator.hardwareConcurrency} </br>`
</script>
复制代码

属性

属性作用
appName浏览器应用程序名称
appCodeName浏览器应用程序代码
product浏览器引擎
appVersion浏览器版本
userAgent浏览器代理
platform浏览器平台
language浏览器语言
onLine浏览器是否在线
cookieEnabled是否启用 cookie
hardwareConcurrency处理器核心数

Navigator 对象的所有属性都不保证返回值是正确的,因为:

不同浏览器能够使用相同名称 导航数据可被浏览器拥有者更改 某些浏览器会错误标识自身以绕过站点测试 浏览器无法报告发布晚于浏览器的新操作系统

但也不需要过度担心,因为常规用户是不会随意操作这些属性的。
Navigator 上面的属性几乎全部都是只读的。即使你更改了属性的值,也不会有任何作用,当然也不会报错。

navigator.appCodeName = ""
console.log(navigator.appCodeName) // "Mozilla"
复制代码

其实 navigator 上的大多数属性都是为了兼容老旧的程序,基本上没有什么实际意义。
比如 appName,它的值只有两个,Netscape(网景) 和 Microsoft Internet Explorer(微软 IE)。其中只有低于 11 版本的 IE 浏览器返回 Microsoft Internet Explorer,其他浏览器都是 Netscape。
比较常用的两个属性是 userAgent 和 onLine。

区分移动设备机型:userAgent

userAgent 可以用来识别不同的平台和浏览器。但是由于用户可以修改这个字符串,各类终端设备的不同等原因,用这个属性来识别浏览器不是个好方法,但它可以大致准确的识别出移动设备的机型。如果自己来处理 userAgent 字符串,需要用到大量正则表达式,而且容易出错,总之是一个非常麻烦的过程。比较推荐解析库 ua-parser-js
它只需要两行代码就可以获取到完整的用户代理信息。

var parser = new UAParser();
console.log(parser.getResult());
复制代码

监听设备在线离线状态:onLine 的用途

window 提供了 online(上线) 和 offline(离线) 两个事件,可以通过监听这两个事件,并配合 onLine 属性进行判断设备是否离线。

window.addEventListener('online',  printNetworkState);
window.addEventListener('offline', printNetworkState);

function printNetworkState() {
  console.log(navigator.onLine ? "您的网络恢复了" : "您的网络断开了");
}
复制代码

调试网络状态可以在浏览器的控制台中选择 Network 面板,点击 WIFI 图标。
image.png
这样就进入了 Network conditions 面板,点击 Network throttling,可以选择不同的网络状态。
image.png
除了上述的属性外,还有几个属性是在 HTML5 中新添加的,在移动开发中比较有实用价值,比如 getBattery(电池状态)、connection(网络连接状态)和 geolocation(地理位置信息)等。

电池状态:getBattery

该 API 可以获取电池状态,从而做一些你想做的事情,比如在电量较低时减少资源消耗,在电量将要耗尽时存储数据,防止数据丢失等。
getBattery 会返回一个 BatteryManager 对象。

属性

属性作用
charging布尔值,表示电池当前是否在充电。
chargingTimeNumber,表示电池充电前地剩余时间,单位为秒,如果电池已经充满电,则为 0。
dischargingTimeNumber,表示电池完全放电和系统暂停前剩余时间,单位为秒。
levelNumber,表示电池充电水平,比例为0.0-1.0之间。

事件

事件作用
onchargingchange电池充电状态更新时触发此事件。
onchargingtimechange电池充电时间更新时触发此事件。
ondischargingtimechange电池放电时间更新时触发此事件。
onlevelchange电池电量更新时触发此事件。

示例:

navigator.getBattery().then(function(battery) {
  function updateAllBatteryInfo(){
    updateChargeInfo();
    updateLevelInfo();
    updateChargingInfo();
    updateDischargingInfo();
  }
  updateAllBatteryInfo();

  battery.addEventListener('chargingchange', updateChargeInfo);
  function updateChargeInfo(){
    console.log(`Battery charging? ${battery.charging ? "Yes" : "No"}`);
  }

  battery.addEventListener('levelchange', updateLevelInfo);
  function updateLevelInfo(){
    console.log(`Battery level: ${battery.level * 100}%`);
  }

  battery.addEventListener('chargingtimechange', updateChargingInfo);
  function updateChargingInfo(){
    console.log(`Battery charging time: ${battery.chargingTime} seconds`);
  }

  battery.addEventListener('dischargingtimechange', updateDischargingInfo);
  function updateDischargingInfo(){
    console.log(`Battery discharging time: ${battery.dischargingTime} seconds`);
  }

});
复制代码

下面是两个在 codepen上面的示例。
充电 Demo 1
充电 Demo 2

网络状态:connection

connection 可以获取系统网络状态,来通过判断网络是蜂窝网还是 WiFi 来提醒用户流量的损耗。还可以通过区分 3G/4G/5G 来提供不同质量的内容。相比于 onLoad 属性,connection 拥有更细粒度的网络状态监控。
示例:

const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

function printConnectionStatus(e) {
  console.log(e.target);
}

connection.addEventListener('change', printConnectionStatus);
复制代码

e.target 是当前的网络状态对象, 它包含如下几个属性。

属性类型作用
downlinknumber网络下行速度,单位 M/s
effectiveTypestring网络类型,常见的值有 4g/3g/2g 等。
onchangenull有值代表网络状态变化,默认为 null
rttnumber往返时间(round-trip time)的缩写,单位是毫秒,表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认,不包含数据传输时间)总共经历的时间。
saveDataboolean打开/请求数据保护模式

地理位置:geolocation

这个 API 只能在 https 协议下才可以使用,它可以获取地理位置。
但这个 API 的位置偏移较为严重,容易产生定位不精准的问题。
示例:

const options = {
  enableHighAccuracy: true,// 高精确度
  timeout: 5 * 1000,// 等待响应的最长时间 单位:毫秒
  maximumAge: 0// 应用程序愿意接受的缓存位置的最长时间
}

if (navigator.geolocation) {
	navigator.geolocation.getCurrentPosition(console.log, console.log, options)
} else {
	console.log('Geolocation is not supported in your browser')
}
复制代码

在任意一个 https 协议的网页控制台中运行上面这段脚本,会在左上角弹出一个地址授权提示框。
image.png
点击允许就完成了授权,这时右侧就会出现一个地址的图标。
image.png
如果浏览器自身没有访问位置的权限,需要在操作系统设置中对其授权,比如在 Mac 系统下。
image.png
完成授权后可以获得一个 GeolocationPosition 对象,这个对象的 coords 属性记录了当前位置信息。
最重要的是经纬度,比如我的:

latitude: 22.3193039
longitude: 114.16936109999999
复制代码

现在可以在高德开放平台使用坐标拾取器查询具体的位置了。
输入经纬度,格式是经度加英文逗号加维度。
image.png
像高德地图、百度地图、腾讯地图这类地图厂商的 H5 SDK 都是使用这个 API 来完成的。

摄像头和麦克风:WebRTC

上述的几个 API 都是实验性的,通常多用于移动端。
除此之外,还有 WebRTC 技术中需要用到的摄像头/麦克风等功能也属于 Navigator 的范畴。
WebRTC 是 Web Real-Time Communication 的缩写,直译为 Web 实时沟通,通常用于视频聊天、语音聊天之类的场景。
但 WebRTC 的内容相对较多,不在这节课中详述,下面是一个 Demo,有兴趣可以自行学习。
WebRTC Demo

Screen

screen 表示当前窗口所在的屏幕,提供了显示设备的信息。
相比较 Navigator 而言,Screen 则简单很多。
它提供了如下属性:

属性作用
heightNumber,屏幕高度,单位像素。通常是常量,缩放网页不会发生变化。但调整屏幕分辨率会影响它。
width屏幕宽度,其他特性同 height。
availHeightNumber,屏幕可用高度,单位像素。即屏幕总高度减掉系统的任务栏或者其它系统组件的高度。
availWidth屏幕宽度,其他特性同 availHeight。
pixelDepth屏幕色彩位数,比如 24 表示 24 位色彩。
colorDepth表示颜色深度。和 pixelDepth 的区别是 colorDepth 表示应用程序的颜色深度,pixelDepth 表示屏幕的颜色深度。绝大多数情况下是一样的。
orientation表示屏幕方向,是个对象。
orientation.angleNumber,屏幕旋转角度。
orientation.typeString,屏幕方向,landscape-primary 横放、landscape-secondary 颠倒的横放、portrait-primary 竖放。

Screen 最常见的用途是根据用户设备的不同,提供不同的资源。
比如根据不同分辨率提供两套 html:

if (screen.width > 1920 && screen.height > 1080) {
  location.replace('max.html')
} else if (screen.width < 1280 && screen.height < 720) {
  location.replace('min.html')
}
复制代码

Location

记录当前 URL 信息,并提供了一系列对当前 URL 的操作方法。

属性

属性作用
href整个 URL。
protocol当前 URL 的协议,包括冒号(:)。
host主机。如果端口不是协议默认的80433,则还会包括冒号(:)和端口。
hostname主机名,不包括端口。
port端口号。
pathnameURL 的路径部分,从根路径/开始。
search查询字符串部分,从问号?开始。
hash片段字符串部分,从#开始。
username域名前面的用户名。
password域名前面的密码。
originURL 的协议、主机名和端口。

这些属性中只有 origin 为只读属性,其它属性均为可写。
当你一旦修改了 location 的属性,页面会立即发生变化。比如你修改了 href,页面会立即触发跳转。

跳转锚点

最常用的用法是通过设置 hash,让页面自动滚动到对应的锚点位置。
只要设置过 id 的元素,都可以称为锚点(anchor)。
示例(2 秒后自动跳转到锚点 d3 的位置):

<div id="d1">1</div>
<div id="d2">2</div>
<div id="d3">3</div>
<div id="d4">4</div>
<div id="d5">5</div>
<div id="d6">6</div>

<style>
  [id^="d"] {
    height: 400px;
    text-align: center;
    line-height: 400px;
    font-size: 24px;
    font-weight: 600;
    color: #fff;
  }
  #d1 {
    background: #d9e3f0;
  }
  #d2 {
    background: #f47373;
  }
  #d3 {
    background: #697689;
  }
  #d4 {
    background: #37d67a;
  }
  #d5 {
    background: #2ccce4;
  }
  #d6 {
    background: #555555;
  }
</style>

<script>
  setTimeout(() => {
    location.href = "#d3";
  }, 2000);
</script>
复制代码

方法

跳转页面 assign

location.assign('http://www.baidu.com')
复制代码

如果参数不是一个合法的 URL,不会跳转页面,而是会抛出异常。

替换当前页面 replace

location.replace('http://www.baidu.com')
复制代码

replace 和 assign 的区别在于,replace 不会存储上一个页面浏览历史,而是用新的 URL 替换掉老的 URL,也就是说意味着页面无法再回退。

重新加载页面 reload

location.reload()
复制代码

reload 的作用等价于点击浏览器的刷新按钮。
image.png
reload 可以传递一个 boolean 类型的参数。
当设置为 true 时会强制从服务器重新请求资源。如果设置为 false,会使用浏览器缓存。默认为 false。

转换为字符串 toString

等同于 location.href。

URL 和 URLSearchParams 对象

一个 URL 可以包含如下几个部分。

image.png

  • href:完整的 URL。
  • protocol:协议,以 : 结尾。常见的有 http 和 https。
  • hostname:域名,和协议以 // 分割,和端口以 : 分割。
  • port:端口,通常会省略掉,默认为 80。
  • pathname:路径,表示网站中的某个资源。
  • search:查询参数,以 ? 开头,彼此之间以 & 拼接。
  • hash:锚点,以 # 开头。

BOM 提供了 URL 和 URL SearchParams 对象来帮助我们更容易地操作 URL。
虽然大多数情况下我们只需要使用字符串类型的 URL 就足够了,但在某些时候 URL 对象非常有用。

创建 URL 对象

需要使用 new 关键字创建,它有两个参数,第一个是 url,或者是一个路径,必选;第二个是基础路径,可选。
比如百度的URL:

new URL('https://www.baidu.com')
复制代码

加上 base 的情况:

new URL('/s?ie=UTF-8&wd=url', 'https://www.baidu.com')
复制代码

url 对象可以在很多情况下替换 url 字符串,因为它会进行隐式转换。
比如:

location.href = new URL('/s?ie=UTF-8&wd=url', 'https://www.baidu.com')
复制代码

解析 URL 字符串

如果你拥有一个 url 字符串,也可以通过 URL 对它进行解析。

const urlStr = "https://www.baidu.com/s?ie=UTF-8&wd=url"

const url = new URL(urlStr)
console.log(url.protocol)// https:
console.log(url.host)// "www.baidu.com"
console.log(url.search)// "?ie=UTF-8&wd=url"
复制代码

编码

在 URL 中,很多特殊的字符串是不合法的,比如中文和空格等。
合法符号如下:

  • URL 元字符:分号(;),逗号(,),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#
  • 语义字符:a-zA-Z0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号('),圆括号(()

除了这两类,其它所有字符都是不合法的。如果你要在字符串中包含不合法字符,都必须转义,规则是根据操作系统的默认编码,将每个字节转为百分号(%)加上两个大写的十六进制字母。而一个中文是三个字节,所以会被转换为 6 个转义字符。
RFC3986 中规定了哪些字符应该被转义。
比如你在地址栏输入:

https://www.baidu.com/s?wd=前端
复制代码

当你拷贝下来之后会发现变成了下面这个格式:www.baidu.com/s?wd=%E5%89…
URL 中前端两个字被转义成了 E5 89 8D 和 E7 AB AF 这 6 个 UTF8 字节码。

字符转义函数

上面这种情况是浏览器自动帮我们做了转义,但如果我们在 JS 中有一个字符串,是未转义的,那么就需要我们自己来转义。
比如你有一个 url 字符串。

const urlStr = "https://www.baidu.com/s?ws=前端"
复制代码

你想跳转到对应的网页。

location.href = urlStr
复制代码

发现会失败,原因就是前端两个汉字没有经过转义。
JavaScript 提供四个 URL 的编码/解码方法。

encodeURI

encodeURI 方法用于编码整个 URL。它的参数是一个字符串,代表整个 URL。它会将元字符和语义字符之外的字符,都进行转义。

encodeURI(urlStr)
// "https://www.baidu.com/s?ws=%E5%89%8D%E7%AB%AF"
复制代码

encodeURIComponent

encodeURIComponent 方法用于转码 URL 的组成部分,会转码除了语义字符之外的所有字符,即元字符也会被转码。所以,**它不能用于转码整个 URL。**它接受一个参数,就是 URL 的片段。

encodeURIComponent('前端')
// "%E5%89%8D%E7%AB%AF"
复制代码

如果你将整个 URL 进行转义,是不可以的。

encodeURIComponent(urlStr)
// "https%3A%2F%2Fwww.baidu.com%2Fs%3Fws%3D%E5%89%8D%E7%AB%AF"
复制代码

上面代码中,encodeURIComponent 会连 URL 元字符一起转义,所以如果转码整个 URL 就会出现问题。

decodeURI

decodeURI 方法用于整个 URL 的解码。它是 encodeURI 方法的逆运算。它接受一个参数,就是转码后的 URL。

const urlEncode = encodeURI(urlStr)
// "https://www.baidu.com/s?ws=%E5%89%8D%E7%AB%AF"
const urlDecode = decodeURI(urlEncode)
// "https://www.baidu.com/s?ws=前端"
复制代码

decodeURIComponent

decodeURIComponent 用于对 URL 片段进行解码。它是 encodeURIComponent 方法的逆运算。它接受一个参数,就是转码后的 URL 片段。

decodeURIComponent('%E5%89%8D%E7%AB%AF')
// "前端"
复制代码

URLSearchParams对象

search 字符串是一段并不是很好处理的文本。
比如我有一个如下 URL:

https://www.baidu.com/s?wd=url&ie=utf-8
复制代码

它的查询参数部分如下:

wd=url&ie=utf-8
复制代码

这段查询参数,我们如果需要替换 ie 的值或者 wd 的值都需要正则匹配或者使用类似的麻烦技术。(在我接触 URLSearchParams 之前就是用这种方式处理的。)
如果我们能够将这段文本通过类似对象的方式操作是最方便的。URLSearchParams 就提供了这种方式。

let url = new URL('https://www.baidu.com/s?wd=url&ie=utf-8');
url.searchParams.set('wd', 'searchparams');
console.log(url);
复制代码

除了 set 方法之外,URLSearchParams 还提供了更多的方法,你可以逐一尝试。

方法作用
append添加新的查询参数
delete删除查询参数
get获取某个查询参数
getAll获取所有查询参数。因为 wd=url&wd=uri 是合法的,所以它会返回一个数组。
has检查某个查询参数是否存在。
set修改某个查询参数。
sort对参数进行排序,很少会用到。

encode 函数与 URL/URLSearchParams

encode 函数和 URL/URLSearchParams 对象之间的区别就是,encode 函数基于 RFC2396,这是一个过时的 URL 规范。而 URL 和 URLSearchParams 是基于最新的 RFC3986
在处理 IPV6 时,encode 就会出现问题。不过这种情况很少见,绝大多数情况下,encode 函数都可以正常运行。

文章分类
前端
文章标签