1.字符串
(1) 常用方法
1.charAt(index) 返回指定位置的字符,若没找到,则返回空
2.charCodeAt(index) 返回指定位置的unicode字符编码,若没找到,则返回空
3.String.concat(str1,str2) 连接多个字符串,并返回新字符串
4.String.fromCharCode(code1) 根据unicode编码,返回字符串
5.indexOf(str) 返回str第一次出现的位置,若没找到,则返回-1
6.lastIndexOf(str) 返回str最后一次出现的位置,若没找到,则返回-1
7.match(regexp) 返回匹配的字符串,若没找到,则返回null
8.search(regexp) 基于正则表达式返回字符串,返回匹配的第一个字符串的·位置
10.slice(start,end) 截取字符串,返回新字符串,不含end
11.split(str,limit) 按照str分割字符串,返回新数组,limit为切割的最大数量
12.substr(start,length) 截取字符串,返回新字符串
13.substring(start,end) 截取字符串,返回新字符串(这个函数与slice基本相同,但这个函数支持start > end,相当于支持逆向截取)
14.toLowerCase() 返回小写字符串
15.toUpperCase() 返回大写字符串
16.trim() 去除字符串两端的空格
17.trimLeft() 去除字符串左边的空格
18.trimRight() 去除字符串右边的空格
19.codePointAt(index) 返回指定位置的unicode编码
20.fromCodePoint(code) 根据unicode编码,返回字符串
21.replace(str,str2){
1.基础用法:本函数基本用法是传入两个参数,匹配字符串中第一个参数,将其替换为第二个参数,返回一个新字符串,不会改变原字符串
2.初级用法:第一个参数可以传入正则表达式,同时第二个参数中可以写入部分特殊的符号,用于匹配特殊的部分,基本如下:
$& -> 该符号报能获取到第一个参数匹配到的字符串
var sStr='讨论⼀下正则表达式中的replace的⽤法';
sStr.replace(/正则表达式/,'《$&》');
// 得到:"讨论⼀下《正则表达式》中的replace的⽤法"
$` -> 获取到匹配项左侧的剩余子串 `
var sStr='讨论⼀下正则表达式中的replace的⽤法';
sStr.replace(/正则表达式/,'《$`》');
// 得到:"讨论⼀下《讨论⼀下》中的replace的⽤法"
$' -> 获取到匹配项右侧的剩余子串'
var sStr='讨论⼀下正则表达式中的replace的⽤法';
sStr.replace(/正则表达式/,"《$'》");
// 得到:"讨论⼀下《中的replace的⽤法》中的replace的⽤法"
$n -> n为数字,获取到第n个匹配项的字符串
var sStr='讨论⼀下正则表达式中的replace的⽤法';
sStr.replace(/正则表达式/g,'《$1》');
// 得到:"讨论⼀下《正则表达式》中的replace的⽤法"
3.高级用法: 本函数第二个参数中可以传入一个回调函数,字符串中匹配了多少次,函数就会执行多少次
函数参数:
第一参数:匹配的字符串
第二参数:匹配的字符串的索引
第三参数:原字符串
这里需要注意,在匹配函数中使用正则表达式并使用分组匹配,这里从第一个参数开始就会将匹配到的每一组都作为参数,直到匹配项传完之后紧接着就会传入上面提到的第二第三
22.repeat(num) 返回字符串重复num次后的新字符串
2.事件流
在dom事件模型中,一个事件发生后,会在子元素与父元素之间传播,这个传播的过程就是事件流。
一般分为三个阶段:
- 事件捕获:事件从最外层的父元素开始,一直传播到目标元素
- 事件目标:事件到达目标元素
- 事件冒泡:事件从目标元素开始,一直传播到最外层的父元素
两种事件
1.事件捕获:
触发元素的事件时,该事件从该元素的祖先元素传递下去。 (不太具体的节点应该更早接收到事件,⽽最具体的节点最后收到事件)
2.事件冒泡:
到达此元素之后,⼜会向其祖先元素传播上去 DOM标准规定事件流包括3个阶段:事件捕获、处于⽬标阶段和事件冒泡。
事件委托
事件委托就是利⽤事件冒泡,只指定⼀个事件处理程序,就可以管理某⼀类型的所有事
件
事件模型
1.原始事件模型
var btn = document.getElementById('.btn');
btn.onclick = fun;
//移除方式
btn.onclick = null;
事件处理直接绑定在元素上,只支持事件冒泡,不支持事件捕获
同一类型的事件,只能绑定一次。
2.标准事件模型
addEventListener(eventType, handler, useCapture)
// 移除方式
removeEventListener(eventType, handler, useCapture)
eventType指定事件类型(不要加on)
handler是事件处理函数
useCapture是boolean类型⽤于指定是否在捕获阶段进⾏处理,⼀般设置为false与IE浏览器保持⼀致
事件捕获:从document⼀直向下传播到⽬标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执⾏。
事件处理:到达⽬标元素, 触发⽬标元素的监听函数。
事件冒泡:从⽬标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执⾏。
支持冒泡的事件:
事件 | 可否支持冒泡 |
---|---|
click | 可以 |
dbclick | 可以 |
keydown | 可以 |
keyup | 可以 |
mousedown | 可以 |
mousemove | 可以 |
mouseout | 可以 |
mouseover | 可以 |
mouseup | 可以 |
scroll | 可以 |
blur | 不可以 |
focus | 不可以 |
resize | 不可以 |
about | 不可以 |
mouseenter | 不可以 |
mouseleave | 不可以 |
load | 不可以 |
unload | bukeyi |
阻止冒泡
阻⽌冒泡⾏为:⾮ IE 浏览器 stopPropagation(),IE 浏览器 window. event. cancelBubble = true。
阻⽌默认⾏为:⾮ IE 浏览器 preventDefault(),IE 浏览器 window. event. returnValue = false。
3.事件代理及委托
把⼀个元素响应事件(click、keydown…)的函数委托到另⼀个元素,在冒泡阶段完成
相对于直接给⽬标注册事件,有以下优点:
- 节省内存,减少dom操作
- 不需要给⼦节点注销事件
- 动态绑定事件
事件委托,会把⼀个或者⼀组元素的事件委托到它的⽗层或者更外层元素上,真正绑定事件的是外层元素,⽽不是⽬标元素。
适合事件委托的事件有:click,mousedown,mouseup,keydown,keyup,keypress
4.HTMLCollection、NodeList
1.NodeList
NodeList对象是⼀个类数组对象,表示⽂档中⼀组有序的节点,⽤于获取⼀组动态变化的节点。
NodeList可以是动态的也可以是静态的,具体取决于获取它的方法。例如,Node.childNodes返回的是一个动态的NodeList,而document.querySelectorAll返回的是一个静态的NodeList。动态的NodeList(如Node.childNodes)会随着DOM结构的变化而自动更新。如果你在页面上添加或删除了节点,那么NodeList的长度和内容也会相应地更新。相反,静态的NodeList(如通过document.querySelectorAll获取的)不会因为DOM的变化而自动更新,即使页面上的元素数量发生了变化,NodeList的长度和内容也不会改变23。
2.HTMLCollection
HTMLCollection对象是⼀个类数组对象,表示⽂档中⼀组有序的元素,⽤于获取⼀组静态的元素
HTMLCollection通常是通过getElementsByTagName等方法获取的,它是一个动态集合(live collection)。这意味着,如果页面的DOM结构发生改变(例如添加或删除元素),HTMLCollection会自动更新以反映这些变化。例如,如果你使用getElementsByTagName获取了一组
- 元素,然后在页面上动态地添加或删除了
- 元素,那么HTMLCollection的长度和内容也会相应地更新。
-
5.Element、Node
1.Element
Element对象是Document对象的⼀个节点,表示的是整个文档树中的⼀个节点。
Element对象是document对象中所有节点的父节点,它有如下属性:- tagName:元素标签名,如div、span等
- attributes:元素属性集合,如class、id等
- children:元素子节点集合,如div中的span等
- outerHTML:元素标签及其子节点,如
html<div class="box"><span>123</span></div>
- innerHTML:元素标签中的子节点,如
html<span>123</span>
2.Node
Node对象是DOM树中的节点,表示的是整个文档树中的⼀个节点。
Node对象是Element对象的父节点,它有如下属性:- parentNode:节点的父节点
- childNodes:节点的子节点集合
- nextSibling:节点的下一个兄弟节点
- previousSibling:节点的上一个兄弟节点
- nodeName:节点的名称,如div、span等
- nodeValue:节点的值,如文本内容
Node是基类,Element、Text都继承于它
6.事件监听
1.onclick类型
onclick类型的事件会绑定到指定的dom元素上,当用户点击该元素时,就会触发事件处理函数,由于这个方式出现的比较早,所以基本上所有浏览器都兼容。
2.addEventListener类型
这种方式用于向文档添加事件句柄(简单来说这个方法就是将需要执行的事件监听函数添加到当前元素的事件监听集合中,以处理指定类型的事件。)这里需要注意的是这个方法可以为一个元素多次添加事件监听,这些事件监听函数都会触发(并且所填加的事件处理函数触发先后顺序并不是代码书写顺序)
const div = document.querySelector('div'); function fn() { console.log(1); } div.addEventListener("click",fn,true) // 1 div.addEventListener("click",fn,true) // 2 div.addEventListener("click",()=> { console.log(13); }) // 3 div.addEventListener("click",()=> { console.log(14); }) // 4
如上面代码所示div元素添加了4个click事件监听,正常情况下,这4个监听函数都会触发。但由于第一和第二个事件监听都用的是fn函数,同时两个事件监听类型,是否冒泡等三个参数均为相同,这就会导致这两个事件监听只会有一个生效。(注意,这里的三个参数相同一定指简单类型和引用类型参数两者都是相同的,如上面代码中的第三第四个监听事件,虽然代码写法完全一致,但由于是在参数位置直接传入现定义的函数,故两者第二个参数是不同的,因此3,4均会执行)。针对以上代码的执行顺序,可以总结为:
- 先执行第三个参数为true的事件监听(若有多个,按代码书写顺序执行)
- 在按代码书写顺序执行第二个参数为false的事件监听
3.两者的区别
- 兼容性: onclick兼容所有浏览器, 添加事件监听兼容IE9及以上浏览器
- 重复性: onclick只能执行一个事件监听,如果多次添加,只有最后一次生效。addEventListener可以添加多个事件监听,并且不会覆盖(相对来说,具体情况看上述代码解析)
- 书写方式: onclick可以作为一个dom属性添加到Html元素上,因此可以直接在html中书写,也可以在js中书写。但addEventListener必须写在js中,且只能写在js中,这个方法主要是为dom元素的事件监听集合中添加监听事件,他被挂载在dom的原型上,因此可以直接使用。
- 触发顺序: onclick事件监听函数触发顺序是按照代码书写顺序执行,addEventListener事件监听函数触发顺序是根据添加的先后顺序执行。(这里的添加顺序的规则主要是先会将所有capture为true的事件添加到一个执行队列,然后才会从头开始再将剩余的事件添加到队列,当触发事件时会依次从队列中取出事件来执行)
- 移除方式: onclick一般会将值社外置位null来移除监听事件。addEventLister必须通过removeEventListener来移除监听事件,且第二个参数和第三个参数要与添加时一致。
7.页面生命周期
1.页面生命周期
HTML页面生命周期包含四个主要事件:
-
DOMContentLoaded事件,HTML文档dom树的解析已经完毕并构建了Dom树,但外部CSS,图片文件可能还没有完全加载完成。
此时当浏览器遇到script标签时,就会停止html解析,转而开始下载解析js。因为脚本可能想要修改DOM,对其执⾏write操作,所以DOMContentLoaded必须等待脚本执⾏结束(这就是js阻塞渲染的原因)。
为script标签添加以下两个属性就可以放止渲染阻塞async: 添加这个属性浏览器就会新开一个线程去开始下载脚本文件,并在脚本下载完成之后立即执行。
defer: 添加这个属性浏览器就会新开一个线程去开始下载脚本文件,但这个属性会使得浏览器在下载完脚本文件之后会等到html解析完成后再执行。
外部样式表不会影响DOM,因为DOMContenLoaded不会等待他们。
但是,如果在样式后⾯有⼀个脚本,那么该脚本必须等待样式表加载完成,原因是,脚本可能想要获取元素的坐标或其他与样式相关的属性。 -
load事件,页面所有资源加载完毕,包括图片、CSS文件等。
当整个⻚⾯,包括样式、图⽚和其他资源被加载完成时,会触发 window 对象上的 load 事件。可以通过 onload属性获取此事件。 -
beforeunload事件,页面即将被卸载。
如果访问者触发了离开⻚⾯的导航(navigation)或试图关闭窗⼝,beforeunload 处理程序将要求进⾏更多确。
认。
如果我们要取消事件,浏览器会询问⽤户是否确定。 -
unload事件,页面已经卸载。
8.异步
1.同步和异步
- 同步: 浏览器在执行js代码的时候,会按顺序执行,再继续执行后续的js代码。即同一时间只执行一段代码,前一个任务必须执行完毕,才能执行后一个任务。
- 异步:当程序执⾏到异步的代码时,会将该异步的代码作为任务放进任务队列,⽽不是推⼊主线程的调⽤栈。等主线程执⾏完之后,再去任务队列⾥执⾏对应的任务。优点是:不会阻塞后续代码的运⾏。
常见场景
- 定时任务
- 网络请求
- 事件监听
解决方案
- 回到函数-回调函数就是将异步代码封装成函数,在需要的时候调用,这样就可以保证异步代码在同步代码执行之后执行。这个最大的缺点就是当需要执行很多的异步代码时会造成代码的嵌套层数很深,代码可读性极差,俗称回调地狱
Promise
promise时在ES6中引入的一种专门用异步处理的解决方案,他本身是一个对象类型,通过new操作符就可以创建一个promise对象,这个对象中包含三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
当一个异步操作操作执行后,就可以通过某种方式来改变promise的状态。
当promise的状态为fulfilled时,可以通过then方法来获取promise对象的结果,当promise的状态为rejected时,可以通过catch方法来获取promise对象的结果。
Promise构造函数接受一个回调函数作为参数,回调函数会挂载两个参数,这连个参数都是回调函数,分别为resolve,reject。调用这个两个函数就会改变promise的状态。
resolve() 会将promise对象的状态从pending变为fulfilled,reject()会将promise对象的状态从pending变为rejected。
因此就可以Promise参数回调函数的位置来执行异步操作,并且判断异步操作是否成功,如果成功就调用resolve(),否则调用reject()。
然后Promise就会返回一个promise对象,如果构造函数中回调函数中异步逻辑执行了,那么这promise的状态就会永久性的敲定,然后我们可以在他的then与catch中通过回到函数来监听promise中状态的改变,从而执行相关的逻辑操作。then中需要擦混入一个回调回调函数,回调函数会被挂载上一个参数res,这个参数就是在前面创建promise中异步代码所执行的结果。catch中也需要擦混入一个回调回到函数,会为回调函数挂载一个err参数,改参数代表若前面异步代码执行出错后的出错原因。
同时then与catch其实不会同时执行,而是会根据promise对象的状态来决定执行哪个回调函数。
promise对象本身还有一个方法,不管是异步操作是否成功,都会执行,即finally方法,该方法同样需要传入一个回调函数,这个回调函数在promise对象状态改变后都会执行。
需要注意的是以上三个方法都返回一个新的promise对象,这个对象的状态会取决于各自的回调函数中的操作。因此这样一来就可以通过链式调用,将多个异步操作串起来。
async/await
前面这种promise的方法来处理异步操作确实比单纯的使用回调函数方便很多,但由于链式编程的原因,我们还是在依赖回调函数来处理,通过链式调用还会造成代码可读性降低。因此,ES7中引入了async/await来处理异步操作。这其实是promise的封装语法糖,首先他的运行必须是在一个函数中,然后通过async将该函数标记为异步函数,在函数中执行异步操作的代码前面添加await关键字,原本需要通过then方法获取一步结果,现在就可以直接由原本的异步操作直接返回(本质上这里其实是js引擎等待异步代码执行完毕后获取到结果在返回,并不是同步执行,只不过还将回调函数变为了底层自动处理,减少了代码书写量),同样如果是catch方法获取数据,await也会直接获取到。
Promise类的其他方法
- Promise.Reject() 直接拒绝,返回一个状态为rejected的promise对象
- Promise.Resolve() 直接通过,返回一个状态为fulfilled的promise对象
- Promise.all() 接受一个promise对象的数组,只有当数组中的所有promise对象的状态都为fulfilled时,返回的promise对象的状态才会为fulfilled,否则返回的promise对象的状态就会为rejected,且返回的promise对象的结果为第一个rejected的promise对象的结果。
- Promise.race() 接受一个promise对象的数组,只要数组中有一个promise对象的状态改变,那么返回的promise对象的状态就会改变,且返回的promise对象的结果为第一个改变状态的promise对象的结果。
9.生成器(Generator)与迭代器(Iterator)
1.迭代器
迭代器,又称迭代器对象,是ES6中新增的一种数据类型。它为js中各种不同的数据结构(Array、Set、Map)提供统⼀的访问机制。部署了Iterator接⼝,就可以完成遍历操作。 因此iterator也是⼀种对象,不过相⽐于普通对象来说,它有着专为迭代⽽设计的接⼝。
更具体地说,迭代器是通过使用 next() 方法实现了迭代器协议的任何一个对象,该方法返回具有两个属性的对象:- 一个是value,表示当前迭代器的值
- 一个是done,表示当前迭代器的状态(这是一个boolean值,当为false时表示迭代序列还可以继续迭代,当为true是表示迭代已经到了最终值)
迭代器对象最主要的方法就是通过next()不断重复的调用获取迭代器对象中的下一个值。
2.生成器
生成器,又称生成器函数,是ES6中新增的一种函数类型。生成器函数与普通函数不同,生成器函数通过function关键字后加一个*号来标识。最初生成器函数不会执行任何逻辑,里面的代码也不会执行,而是在调用生成器函数后会返回一个特殊的迭代器,这个迭代器就被称为生成器。我们接下来调用这个生成器的next() 方法,就会消费掉这个生成器,然后生成器函数中的逻辑才会执行,执行过程中遇到第一个yield语句就会暂停函数代码的执行,从而又返回一个生成器,以此类推,直到整个函数执行完毕。
返回的生成器函数的值就是yield语句后面的值(这个关键字和return关键字很像,但他不会结束函数运行,而是暂停函数运行,返回生成器)
3.可迭代对象
若一个对象拥有迭代行为,则被称为可迭代对象。如js内置的Array、Set、Map等。为了实现迭代,这个对象必须部署了Symbol.iterator属性,该属性返回一个迭代器。
for … of就是采用对象的Symbol.iterator属性来遍历可迭代对象。
- 手写for…of
function forOf(obj,callback) { let iterator = obj[Symbol.iterator]() while(true) { const res = iterator.next() if(res.done) break; callback(res.value) } } forOf([1,2,3,4,5],(data) => { console.log(data); })
对于原本不具有迭代器的对象,我们可以通过部署Symbol.iterator属性来使其具有迭代器行为。
const obj = {} obj[Symbol.iterator] = function* () { yield 1 yield 2 yield 3 } for(let i of obj) { console.log(i); }
4.yield与return异同点
相同点: 都可以返回值,且都会暂停函数执行。
不同点: yield可以返回多次值,而return只能返回一次值。
都能返回紧跟在语句后⾯的那个表达式的值
yield相⽐于return来说,更像是⼀个断点。遇到yield,函数暂停执⾏,下⼀次再从该位置继续向后执⾏,⽽
return语句不具备位置记忆的功能。
⼀个函数⾥⾯,只能执⾏⼀个return语句,但是可以执⾏多次yield表达式。
正常函数只能返回⼀个值,因为只能执⾏⼀次return;Generator 函数可以返回⼀系列的值,因为可以有任意多个yield特别注意:箭头函数不可以做生成器函数。
手写模块
- 手写数组forEach方法
Array.prototype.myForEach = function (callback) { // 获取迭代器对象 const iterator = this[Symbol.iterator]() let index = 0 while(true) { const res = iterator.next() if(res.done) break; callback(res.value,index,this) index++ } }
10.定时器
1.setTimeout;
setTimeout(⭐ 必选参数) 是一个挂载到全局对象上的一个方法,他开启一个定时器(这里我更加习惯叫他延时器),定时器会在一定时间之后执行,同时本身返回一个定时器的ID,使用该ID我们可以取消定时器的设置。
参数:
- functionRef: 定时器到需要执行的函数逻辑
- delay: 延时时间,单位是毫秒,如果不传这个这个 参数,默认是0,这个值有一个最小值就是4ms,如果小于这个值,会被自动设置为4ms,0除外
- param1, …, paramN: 此后的参数都是作为定时器所执行的函数的参数传递
返回:
- 定时器的ID,可以用来取消定时器(这个ID本质上是一个正整数。
取消:
setTimeOut的取消可以调用全局对象上的clearTimeout方法,该方法接收一个参数,就是定时器的ID。
this指向:
由 setTimeout() 执行的代码是从一个独立于调用 setTimeout 的函数的执行环境中调用的。为被调用的函数设置 this 关键字的通常规则适用,如果你没有在调用中或用 bind 设置 this,它将默认为 window(或 global)对象。
简单来说就是对于传入的函数this指向全局对象,如果需要改变this指向,可以对传入的函数使用bind方法。注意: 不能为setTimeout使用call等方法来改变其this,因为setTimeout是挂载在全局对象上的,而call等方法会改变this指向,这是不被允许的,就算改变了setTimeout的this指向,其中调用的函数还是指向全局对象,因为两者在调用时不是处于同一执行环境中。
解决方法:这里可以使用包装函数来解决this问题,也可以使用箭头函数,或者bind方法
setTimeout(function() { obj.fn() // 这样fn就指向obj了 }) setTimeout(obj.fn) // 这样fn中的this指向全局对象 //也可以这样写 setTimeout(obj.fn.bind(obj)) // 这样fn中的this指向obj setTimeout(() => { obj.fn() // 这样fn中的this指向ob })
这里还有一个需要注意的点,就是setTimeout执行函数的位置本质上可以传入一个可执行的js字符串代码,与eval相似,但是现存许多安全问题,因此许多文档中不建议使用,传递给 setTimeout() 的字符串是在全局上下文中求值的,因此当字符串被求值为代码时,setTimeout() 被调用的上下文中的局部符号将不可用。
详情可参考MDN官网:
延时比指定值更长的原因:
- 嵌套超时:
一旦对 setTimeout 的嵌套调用被安排了 5 次,浏览器将强制执行 4 毫秒的最小超时。 - 定时器队列:
如果页面(或操作系统/浏览器)正忙于其他任务,超时也可能比预期的晚。需要注意的一个重要情况是,在调用 setTimeout() 的线程结束之前,函数或代码片段不能被执行。
function foo() { console.log("foo 被调用"); } setTimeout(foo, 0); console.log("setTimeout 之后");
出现这个结果的原因是,尽管 setTimeout 以 0ms 的延迟来调用函数,但这个任务已经被放入了队列中并且等待下一次执行;并不是立即执行;队列中的等待函数被调用之前,当前代码必须全部运行完毕,因此这里运行结果并非预想的那样。
最大延时时间
浏览器内部以 32 位带符号整数存储延时。这就会导致如果一个延时大于 2147483647 毫秒(大约 24.8 天)时就会溢出,导致定时器将会被立即执行。
兼容市面上所有的浏览器
2.setInterval
setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
参数:
- functionRef: 定时器到需要执行的函数逻辑
- delay: 延时时间,单位是毫秒,如果不传这个这个 参数,默认是0,这个值有一个最小值就是4ms,如果小于这个值,会被自动设置为4ms,0除外
- param1, …, paramN: 此后的参数都是作为定时器所执行的函数的参数传递
返回:
- 定时器的ID,可以用来取消定时器(这个ID本质上是一个非零数值。)
取消:
setInterval的取消可以调用全局对象上的clearInterval方法,该方法接收一个参数,就是定时器的ID。
⭐注意: setTimeout与setInterval的定时器共用同一个ID池,并且clearInterval与clearTimeout两者在技术实现上时互通的,因此两者其实都可以用于取消上述两种定时器的任何一种,但官方标准上建议我们匹配使用,避免代码杂乱无章。
this指向:
y与setTimeout类似定时器是可以嵌套的;这意味着,setInterval() 的回调中可以嵌入对 setInterval() 的调用以创建另一个定时器,即使第一个定时器还在运行。为了减轻这对性能产生的潜在影响,一旦定时器嵌套超过 5 层深度,浏览器将自动强制设置定时器的最小时间间隔为 4 毫秒。如果尝试将深层嵌套中调用 setInterval() 的延迟设定为小于 4 毫秒的值,其将被固定为 4 毫秒。
当浏览器标签页处于后台或不活跃状态时,浏览器可能会延迟执行定时器任务,以节省资源。这种情况下,定时器的执行时间可能会比预期的长。