JavaScript 浏览器对象模型BOM

118 阅读8分钟

前言

本文主要介绍一些BOM中常见API的用法,包括window,定时器,location,navigator,history

window

  1. 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'
  1. 像素比

现在大多数的设备分辨率都会很高,比如手机的1920x1080或者2k,4k,如果以正常的像素去显示内容的话,那么内容会缩小很多,所以浏览器会将设备的分辨率降为较低的逻辑分辨率,比如1440x900。那么以实际的物理像素值除以逻辑像素值就可以的到像素比

window.devicePixelRatio
  1. 浏览器的视口大小
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
  }
}
  1. 视口位置

由于浏览器视口尺寸无法正常显示整个页面的内容,所以可以通过滚动在有限的视口中查看文档,度量文档相对于视口的滚动距离的属性有两对,都返回相同的值

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的值)时

  1. 导航与打开新窗口

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弹窗拦截,将下面的弹出式窗口和重定向关闭即可

截屏2022-04-14 下午10.39.26.png

  1. 定时器

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/…

  1. 查询字符串 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']
}

  1. 操作地址 可以使用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

  1. 导航 history对象提供了方法,让我们在历史记录中沿任何方向导航,前进和后退不会产生新的记录,只是让页面在记录间跳转

history.go() 传入需要前进或者后退的页数,正数前进,负数后退

history.back() 后退一页

history.forward() 前进一页

history.length 记录了历史记录的条数,对于窗口或者标签页中加载的第一个页面,该值为1,可以依据此确定浏览器的起点是不是你的页面

  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>

截屏2022-04-15 上午1.30.05.png

总结

本文介绍了浏览器BOM中常用的API以及细节,都是工作中常用的,特别是定时器和SPA的导航实现基础,同样的,希望大家养成看书,看文档的好习惯

参考文献

  1. JavaScript高级程序设计
  2. developer.mozilla.org/zh-CN/docs/…
  3. developer.mozilla.org/zh-CN/docs/…
  4. developer.mozilla.org/zh-CN/docs/…
  5. developer.mozilla.org/zh-CN/docs/…