bom

浏览器对象模型

BOM 可以使我们通过 JS 来操作浏览器, 在 BOM 中为我们提供了一组对象,用来完成对浏览器的操作

BOM 对象

Navigator

火狐的 userAgent

Chrome 的 userAgent

IE8

IE9

IE10

IE11

if(/firefox/i.test(ua)){
  alert("你是火狐")
}else if (/chrome/i.test(ua)){
  alert("你是chrome")
}else if (/msie/i.test(ua)){
  alert("你是IE浏览器")
}else if ("ActiveXObject" in window){
  alert("IE11 funny")
}

Location

Console

console.log()

console.warn()

console.time() 和 console.timeEnd()

console.time('timer1');
// some code
console.timeEnd('timer1');

console.trace()

定时器

delay 参数字符串形式的也是合理的

image-20210107145133926

setInterval()

可以将一个函数,每隔一段时间执行一次

参数:

  1. 回调函数,该函数会每隔一段时间被调用一次
  2. 每次调用间隔的时间,单位是毫秒

返回值:

clearInterval()

可以用来关闭一个 setInterval() 循环定时器

方法中需要一个循环定时器的标识作为参数,这样将关闭标识对应的定时器

clearInterval(timer)

setTimeout()

  var timer = setTimeout(function(){console.log(num++);},3000);

延时调用一个函数不马上执行,而是隔一段时间以后在执行,而且只会执行一次

延时调用和定时调用的区别,定时调用会执行多次,而延时调用只会执行一次

延时调用和定时调用实际上是可以 互相代替 的,在开发中可以根据自己需要去选择

clearTimeout(timer)

参数

定时器的注意点

目前,我们每点击一次按钮,就会开启一个定时器,点击多次就会开启多个定时器,这就导致图片的切换速度过快,并且我们只能关闭最后一次开启的定时器

在开启定时器之前,需要将当前元素上的其他定时器关闭

定时器和消息队列

第二个参数是一个表示等待多长时间的毫秒数,但经过该时间后指定的代码不一定会执行

JavaScript 是一个单线程序的解释器,因此一定时间内只能执行一段代码。为了控制要执行的代码,就有一个 JavaScript 任务队列。这些任务会按照将它们添加到队列的顺序执行

setTimeout() 的第二个参数告诉 JavaScript 再过多长时间把当前任务添加到队列中 (添加的过程是异步的)

如果队列是空的,那么添加的代码会立即执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行

为什么要用 Timeout 代替 Interval?

考虑极端情况,假如定时器里面的代码需要进行大量的计算,或者是 DOM 操作。

这样一来,花的时间就比较长,有可能前一次代码还没有执行完,后一次代码就被添加到队列了。

假如时间间隔为 100 毫秒,要执行的代码需要 300 毫秒,如下图所示:

一开始执行 setInterval, 100 毫秒后将要执行的代码添加到队列。

100 毫秒时,执行代码进入队列,队列空闲,定时器内的代码执行。

200 毫秒时,第一次的定时器代码还在执行当中。第二次的定时器代码被推入事件队列,等待队列空闲,然后执行。

300 毫秒时,第一次的定时器代码还在执行中,第二次的定时器代码在事件队列末端等待执行。因为该定时器已经有第二次的代码在队列中等待了,所以这一次的代码不会被推入队列,被忽略了。

400 毫秒时,第一次的定时器代码执行完毕,队列空闲,下一个等待的代码执行,第二次的定时器代码开始执行。

捋一捋,这里的第一次的代码和第二次代码的间隔并没有预期的 100 毫米,而是第一次的执行完,第二次的立马执行了。因为第一的代码还没执行完,第二次的代码就已经在队列中等待了。

关于被忽略的第三次定时器代码,因为 300 毫秒时,这个定时器已经有第二次的代码在等待了,而只有当没有该定时器的代码在队列中时,该定时器新的代码才能去排队,所以第三次不会被添加到队列中。

由上可见,在这种极端情况下,setIntreval 实现不了需求

用 setTimeout 实现 setInterval

而 setTimeout 可以实现 setInterval 的功能,并且不会出现上面的情况。

const repeat = (func, ms) => {
  setTimeout(() => {
    func()
    repeat(func, ms)
  }, ms)
}

上面的 repeat 接受两个参数,func 是要以间隔时间执行的函数,ms 代表间隔的毫秒数。

repeat 内部用 setTimeout 在指定的毫秒数 ms 之后,将匿名函数推入事件队列,匿名函数中包含要执行的 func , 以及 repeat(func, ms)。匿名函数执行时,先执行 func, 然后递归调用 repeat 来模拟 setInterval,递归调用的 repeat 中,又将执行 setTimeout, 在 ms 毫秒后,将下一次的定时器代码推入队列末端。

这里可以注意到,将下一次定时器代码推入队列时,上一次的代码无论如何都已经执行完了,所以不会重蹈 setInterval 的覆辙。

但是这种实现方式,不能清除定时器,还需要改造一下。

setTimeout 方式的改良

function Timer() {
  this.timeID = null
  this.func = null
}

Timer.prototype.repeat = function(func, ms) {
  if (this.func === null) {
    this.func = func
  }

  // 确保一个 Timer 实例只能重复一个 func
  if (this.func !== func) {
    return
  }

  this.timeID = setTimeout(() => {
    func()
    this.repeat(func, ms)
  }, ms)
}

Timer.prototype.clear = function() {
  clearTimeout(this.timeID)
}

const a = () => console.log('a')

const b = () => console.log('b')

const timer = new Timer()

timer.repeat(a, 1000)
timer.repeat(b, 1000) // 不会定时执行 b

上面的代码定义了构造函数 Timer, 其产生的实例有两个属性,timeID 用来存储 setTimeout 返回的值,也就是定时器的 ID; func 则用来存储需要被定时执行的函数。

Timer 的 prototype 上定义了 repeat 方法,和之前的 repeat 函数大致一样。只是在开头,将传入的 func 存到了 this.func. repeat 递归调用时,每执行一次 setTimeout , this.timeID 就会取得最新的定时器的 ID.

Timer 的 prototype 上还定义了 clear 方法,用来清除定时器,。

注意到 repeat 和 clear 两个方法,定义时用的是函数声明,而不是匿名函数。之所以这样做,是为了保证方法内 this 指向 Timer 实例。如果用的是箭头函数,这两个方法里面的 this 将指向全局对象 Window, 更多和 this 有关的,可参考这篇博客。

接下来定义了两个简单的函数 a 和 b.

再 new 一个 Timer 赋值给变量 timer, timer 执行 repeat 方法定时运行 a 和 b .

可以发现,每隔大约一秒,就会执行一次 a, 控制台就会打印一次 a. 而 b 则不会定时重复执行。因为一个 Timer 实例只能重复执行一个函数。假如不作限制,a 和 b 重复,那么在 clear 的时候,只能清除掉最后添加进来的重复代码的定时器 ID.

假如要重复执行 b, 只能再实例化一个 Timer.

总结

因为 setInterval 在一些情况下,会导致前后两次定时器代码的执行间隔产生不可预料的变化,甚至会跳过某次定时器代码的执行,所以可用 setTimeout 实现 setInterval 的功能。

定时器和浏览器进程

考虑网页失焦和浏览器被切后台

如果做过秒杀倒计时的项目大概就不会起这个标题了吧。切后台或者多一级激活 webview 的时候 settimeout 会暂停执行,而 setinterval 不会。

倒计时的纠偏实现

在前端实现中我们一般通过 setTimeout 和 setInterval 方法来实现一个倒计时效果。但是使用这些方法会存在时间偏差的问题,这是由于 js 的程序执行机制造成的,setTimeout 和 setInterval 的作用是隔一段时间将回调事件加入到事件队列中,因此事件并不是立即执行的,它会等到当前执行栈为空的时候再取出事件执行,因此事件等待执行的时间就是造成误差的原因。

一般解决倒计时中的误差的有这样两种办法:

(1)第一种是通过前端定时向服务器发送请求获取最新的时间差,以此来校准倒计时时间。

(2)第二种方法是前端根据偏差时间来自动调整间隔时间的方式来实现的。这一种方式首先是以 setTimeout 递归的方式来实现倒计时,然后通过一个变量来记录已经倒计时的秒数。每一次函数调用的时候,首先将变量加一,然后根据这个变量和每次的间隔时间,我们就可以计算出此时无偏差时应该显示的时间。然后将当前的真实时间与这个时间相减,这样我们就可以得到时间的偏差大小,因此我们在设置下一个定时器的间隔大小的时候,我们就从间隔时间中减去这个偏差大小,以此来实现由于程序执行所造成的时间误差的纠正。

详细资料可以参考: 《JavaScript 前端倒计时纠偏实现》

定时器为什么不精准?

避免旁路攻击

Window 一些常用方法

alert()

confirm()

prompt() 返回的是用户输入的内容的 字符串形式

eval() 函数

es-function

open()

Window:open() 方法 - Web API 接口参考 | MDN

HTML页面导航全过程 - 掘金

let windowObjectReference = window.open(strUrl, strWindowName, [strWindowFeatures]);

WindowObjectReference: 打开的新窗口对象的引用。如果调用失败,返回值会是 null。如果 父子窗口满足“同源策略”,你可以通过这个引用访问新窗口的属性或方法。

strUrl: 新窗口需要载入的 url 地址。strUrl 可以是 web 上的 html 页面也可以是图片文件或者其他任何浏览器支持的文件格式。

strWindowName: 新窗口的名称。该字符串可以用来作为 超链接 或表单元素的目标属性值。字符串中不能含有空白字符。注意:strWindowName 并不是新窗口的标题。

strWindowFeatures: 可选参数。是一个字符串值,这个值列出了将要打开的窗口的一些特性 (窗口功能和工具栏) 。 字符串中不能包含任何空白字符,特性之间用逗号分隔开。参考下文的 [位置和尺寸特征](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/open#Position and size features)。

escape,encodeURI,encodeURIComponent 有什么区别

《escape,encodeURI,encodeURIComponent 有什么区别?》

BOM 规范

缺少事实上的规范导致 BOM 有很多问题,因为浏览器提供商会按照各自的想法随意去扩展它。W3C 为了把浏览器中,JavaScript 最基本的部分标准化,已经将 BOM 的主要方面纳入了 HTML5 的规范中。

Window 对象

全局变量与 Windows 对象属性的差别

window.open()/close 方法

JSON

JSON:JavaScript Object Notation JS 对象表示法。JS 中的对象只有 JS 自己认识,其他的语言都不认识

JSON 就是一个特殊格式的字符串,这个字符串可以被任意的语言所识别,并且可以转换为任意语言中的对象,JSON 在开发中主要用来数据的交互

JSON 和 JS 对象的格式一样,只不过 JSON 字符串 中的 属性名必须加双引号,本质是一个字符串,所以最外层整个再套一个双引号

分类:

  1. 对象 {}
  2. 数组 []

JSON 中允许的值:

  1. 字符串
  2. 数值
  3. 布尔值
  4. null
  5. 对象
  6. 数组

在 JS 中,为我们提供了一个工具类,就叫 JSON,这个对象可以帮助我们将一个 JSON 转换为 JS 对象,也可以将一个 JS 对象转换为 JSON

JSON.parse()

JSON.stringify()

Web 通信

概述

通信事件

该事件包含 5 个只读属性:

data 包含任意字符串数据,由原始脚本发送
origin 一个字符串,包含原始文档的方案、域名以及端口 (如:http://domain.example:80)
lastEventId 一个字符串,包含了当前的消息事件的唯一标识符。
source 原始文件的窗口的引用。更确切地说,它是一个 WindowProxy对象
ports MessagePort 对象数组,表示消息正通过特定通道(数据通道)发送的相关端口(适用于通道消息传输或者向一个共享线程(shared work )发送消息时)。

在跨文档通信和通道通信中,lastEventId 的值一般是个空字符串;lastEventId 应用在服务器端发送事件上。发送信息中如果没有 ports, 则 ports 属性值就是个长度为 0 的数组。

MessageEvent 继承 DOM事件接口,且属性共享。然而,通信事件并没有冒泡,不能取消,也没有默认行为。

获取窗口引用

Iframe 窗口

contentWindow

Window.frames

Open 窗口

bom

messageEvent.sorce

Worker

window.postMessage

语法

otherWindow

message

targetOrigin

transfer

示例

安全问题

MessageChannel API

背景

MessageChannel()

MessagePort 对象

转发端口

示例

缓冲

BroadcastChannel API

其他

Geolocation

FAQ

#faq/js

使用 Settimeout 模拟 Setinterval

(function foo() {
    ...
    setTimeout(foo, delay);
})();

同时也是一个 立即执行 的 setInterval