前言
本文主要介绍一些BOM中常见API的用法,包括window,定时器,location,navigator,history
window
- Global作用域
window对象既代表浏览器实例也代表JavaScript中的Global对象,正是因为window对象被复用为
Global
对象,所以通过var声明的全局变量和函数或者function声明的函数都会变成window的属性和方法,但是通过let和const定义的属性不会
var a = 'a'
function b() {
console.log('b')
}
var c = () => {console.log('c')}
console.log(window.a) // 'a'
window.b() // 'b'
window.c() // 'c'
- 像素比
现在大多数的设备分辨率都会很高,比如手机的1920x1080或者2k,4k,如果以正常的像素去显示内容的话,那么内容会缩小很多,所以浏览器会将设备的分辨率降为较低的逻辑分辨率,比如1440x900。那么以实际的物理像素值除以逻辑像素值就可以的到像素比
window.devicePixelRatio
- 浏览器的视口大小
let pageWidth = window.innerWidth,
pageHeight = window.innerHeight
if (typeof pageWidth !== 'number') { // 浏览器是否提供了视口信息
if (document.compatMode === 'CSS1Compat') { // 浏览器是否处于标准模式
pageWidth = document.documentElement.clientWidth
pageHeight = document.documentElement.clientHeight
} else {
pageWidth = document.body.clientWidth
pageHeight = document.body.clientHeight
}
}
- 视口位置
由于浏览器视口尺寸无法正常显示整个页面的内容,所以可以通过滚动在有限的视口中查看文档,度量文档相对于视口的滚动距离的属性有两对,都返回相同的值
window.scrollX
window.scrollY
// 下面两个已经不推荐被使用了
window.pageXOffset
window.pageYOffset
如果要滚动视口的位置可以使用window.scroll(),window.scrollTo(),window.scrollBy() scroll()和scrollTo()方法是相同的都是滚动到绝对位置,scrollBy()方法是相对于当前视口位置去滚动,可以通过配置控制滚动行为
window.scrollBy({
left: 0,
top: 100,
behavior: 'auto',
})
// 平滑滚动
window.scrollBy({
left: 0,
top: 100,
behavior: 'smooth',
})
顺便提一下可以通过css实现平滑滚动
scroll-behavior: smooth
,适用于当用户手动导航(锚点跳转)或者 CSSOM scrolling API 触发滚动操作(修改元素内部scrollTop或者scrollLeft的值)时
- 导航与打开新窗口
window.open()可以用于导航到指定的url,也可以用于打开新浏览器窗口,该方法只接收三个参数,要加载的url,目标窗口,特性字符串。第二个参数如果是一个已经存在的窗口名,则会在对应的窗口打开url,反之就会在新窗口打开。
第二个参数也可以是特殊的窗口名_self,_parent,_top,_blank。
该方法的返回值是新窗口的window,可以使子窗口的window.close()关闭新窗口,子窗口的window.opener属性表示打开该窗口的窗口,如果两个窗口要通信则标签不能运行在独立的进程中,如果把该属性设置为null则表示该窗口运行在独立的进程中
如果弹窗被插件屏蔽了,window.open()可能会返回null,而且可能会抛出错误,我们可以通过如下方式判断
let blocked = false
try {
let subWin = window.open('https://www.baidu.com', '_blank')
if(subWin === null) {
blocked = true
}
} catch (error) {
blocked = true
}
if(blocked) {
alert('blocked')
}
可以通过如下设置开启chorme弹窗拦截,将下面的弹出式窗口和重定向关闭即可
- 定时器
setTimeout()用于指定一定时间后执行某些代码,setInterval()用于指定每隔一段时间后执行某些代码。由于 JavaScript是单线程的,所以每次只能执行一段代码,为了调度不同代码的执行, JavaScript维护了一个任务队列,其中的任务会按照添加到队列的先后顺序执行。
setTimeout()的第一个参数是指定要执行的代码,第二个参数是指定将要执行的代码加入到队列之前要等待的时间,如果队列是空的,则会立即执行,否则要等队列前的任务都执行完了才会执行,所以定时器不一定是准的,可能会被前面的任务阻塞,同样对于setInterval()也是,该方法只会在固定的间隔时间将任务加入队列,并不关心任务是否执行了,花了多少时间。
setTimeout()和setInterval()的回调函数都会在全局作用域的一个匿名函数中执行,那么函数中的this就是window,严格模式下为undefined,如果指定的回调是一个箭头函数,那么this是箭头函数定义时外部函数的this
setTimeout()和setInterval()执行后都会返回一个ID用于取消该定时器,setInterval()的取消比较重要,毕竟不取消会一直执行到页面关闭
生产环境中设置循环任务的推荐做法
let num = 0
let max = 10
let increamentNumber = function () {
console.log(num++)
if (num < max) {
setTimeout(() => {
increamentNumber()
}, 500)
} else {
console.log('done')
}
}
setTimeout(() => {
increamentNumber()
}, 500)
这样可以保证这个任务结束和下一个任务之间的时间间隔,如果是setInterval()添加的任务,可能会因为上一次回调执行时间太长而连续执行。而且有些循环任务会因此而被跳过,比如在队列里的任务执行完之前调用了clearInterval()
还可将上面的逻辑封装一下
function intervalFoo(fn, max, delay) {
if (typeof max !== 'number' || max <= 0) return
let num = 0
function bar() {
fn(num)
num++
if (num < max) {
setTimeout(() => {
bar()
}, delay)
}
}
setTimeout(() => {
bar()
}, delay)
}
intervalFoo(
num => {
console.log(num)
},
5,
500
)
location
location对象提供了当前窗口中加载的文档的信息,还包括了把URL解析成离散对象后能通过属性访问的值,比如hash,host,search,pathname,origin等等具体参考developer.mozilla.org/zh-CN/docs/…
- 查询字符串
location中包含了查询字符串的信息,我们在项目中经常需要解析查询字符串,现在浏览器给我们提供了标准的API
URLSearchParams
,它可以根据查询字符串创建一个支持特定操作方法的实例对象,而且这个对象还是提供了我们喜欢的迭代接口
let qs = '?name=cyf&age=18&money=100000000000&hometown=changde'
let qsObj = new URLSearchParams(qs)
console.log(qsObj.has('name')) // true
console.log(qsObj.get('age')) // 18
qsObj.delete('name')
console.log(qsObj.toString()) // age=18&money=100000000000&hometown=changde&weight=300
for (const i of qsObj) {
console.log(i)
// ['age', '18']
// ['money', '100000000000']
// ['hometown', 'changde']
// ['weight', '300']
}
- 操作地址 可以使用location.assign()传入一个url修改地址,并且在浏览器历史记录中添加一条记录,如果给location.herf或window.location设置一个url,同样也会以该url值调用assign方法,通常我们使用location.herf
修改location的属性也会让页面重新加载url,除了location.hash,但是location.hash的修改也同样会被加入到历史记录,并会触发hashChange事件,正是因为如此,现在的单页应用就可以利用此特性实现导航(路径改变,不刷新页面)
location.replace()使用这个方法修改的url会替换当前的历史记录
location.reload()使用这个方法来刷新页面,如果不传参数则会以最有效的方式加载页面,比如从缓存中加载,如果穿入true则会从服务器加载。需要注意的是,该方法后面的代码坑能执行,也可能不会执行,取决于网络延迟和系统资源等因素,所以最好把location.reload()作为最后一行代码
navigator
navigator可以用来实现用户代理检测,插件检测,注册处理程序
比如navigator.userAgent返回浏览器的用户代理字符串
history
history对象表示当前窗口首次使用以来用户的导航历史记录,但是出于安全考虑,这个对象不会暴露用户访问过的url
- 导航 history对象提供了方法,让我们在历史记录中沿任何方向导航,前进和后退不会产生新的记录,只是让页面在记录间跳转
history.go() 传入需要前进或者后退的页数,正数前进,负数后退
history.back() 后退一页
history.forward() 前进一页
history.length 记录了历史记录的条数,对于窗口或者标签页中加载的第一个页面,该值为1,可以依据此确定浏览器的起点是不是你的页面
- 历史状态管理 hashChange事件会在url的hash值改变时触发
状态管理API可以让开发者改变浏览器url而且不会加载新页面,同样利用这个可以实现单页应用的导航
history.pushState() 此方法接收3个参数,一个state对象(大小限制在500KB~1MB),一个新状态的标题(暂时不被使用),和一个可选的相对url,当history.pushState()方法执行后,浏览器的地址栏也会改变成相对url。
当点击了浏览器工具栏的后退或者前进按钮后会触发window对象上的popstate事件。可以通过事件对象获得上次pushState()传入的state对象。所以我们可以根据state来恢复上个页面的状态。但是初次加载的页面没有这个状态对象,当退到第一个页面时state的值为null
在pushState()执行后的页面刷新页面会404(本人使用vscode插件的live-server打开的),这也是单页应用使用 history api需要解决的问题之一
history.replaceState()更新当前的状态对象和路径,而不创建新的历史记录
可以通过history.state来直接获取当前的状态对象
可以根据以下代码实现来验证前面的概念,首先点击pushState按钮,然后点击后退,再点击后退,然后点击前进,然后点击replaceState会的到截图中的输出
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn1">pushState</button>
<button id="btn2">replaceState</button>
<script>
const btn1 = document.getElementById('btn1')
history.pushState({ name: 'bar' }, '', '/bar')
btn1.onclick = function () {
history.pushState({ name: 'foo' }, '', '/foo')
}
window.addEventListener('popstate', e => {
console.log(e.state)
})
const btn2 = document.getElementById('btn2')
btn2.onclick = function () {
history.replaceState({ name: 'fooc' }, '', '/fooc')
console.log(history.state)
}
</script>
</body>
</html>
总结
本文介绍了浏览器BOM中常用的API以及细节,都是工作中常用的,特别是定时器和SPA的导航实现基础,同样的,希望大家养成看书,看文档的好习惯