定时执行代码的功能。
运行机制:
setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop时重新判断。
setTimeout和setInterval都是把任务添加到“任务队列”的尾部。因此,它们实际上要等到当前脚本的所有同步任务执行完,然后再等到本次Event Loop的“任务队列”的所有任务执行完,才会开始执行。
4ms
settimeout();
var timerId = settimeout(function, delay毫秒, …args);
IE 9.0以下版本,只允许setTimeout有两个参数
定义:在delay毫秒后,执行一次function,并且可以传递一些参数给function。settimeout会返回一个id整数(表示定时器的编号),可以用来取消定时器。
清除:clearTimeout(timerId1);
特点:它只执行一次。settimeout的延迟时间并不是准确的,它只是表示最早可能执行的时间。
setinterval();
var timerId2= setinterval(function, interval毫秒, …args);
定义:每隔interval毫秒,执行一次function,并且可以传递一些参数给function。setinterval也会返回一个id整数(表示定时器的编号),可以用来取消定时器。
清除:clearInterval(timerId2);
特点:它会不断重复执行,直到被清除。setinterval的执行间隔并不是准确的,它也会受到其他代码的影响,比如浏览器的事件循环,或者其他的定时器。如果function的执行时间超过了interval,那么setinterval会累积一些未执行的任务,导致后续的执行时间不稳定,甚至出现堆积的情况。
例如,下面的代码会每隔1秒打印出一个数字,从1开始递增,但是如果function的执行时间超过了1秒,或者浏览器对setinterval进行了优化,那么打印出的数字可能会不连续,或者不按照预期的顺序。
区别:
settimeout只执行一次,而setinterval会不断重复执行,直到被清除。
settimeout和setinterval的延迟时间和执行间隔都不是准确的,它们会受到其他代码和浏览器的影响。
settimeout和setinterval的执行顺序也不是确定的,它们会受到浏览器的优化策略的影响。
’Q1: 为什么我的setInterval函数在浏览器的非激活标签中运行得慢?
答: 大多数现代浏览器为了优化性能和减少电池消耗,当标签页不在前台或被最小化时,会自动减慢setInterval和setTimeout的执行频率。如果需要在非激活标签中保持正常的执行频率,可以考虑使用Web Workers,因为它们在后台线程中运行,不受此限制。
Q2: 是否可以同时使用setTimeout和setInterval?
答: 当然可以。setTimeout和setInterval可以根据需求同时使用。例如,你可能想使用setInterval进行轮询,而在某些特定情况下使用setTimeout进行单次的延迟操作。
Q3: setTimeout中设置的0毫秒的延迟意味着函数会立即执行吗?
答: 不一定。虽然setTimeout的延迟时间被设为0毫秒,但由于JavaScript的事件循环机制,它仅仅是将回调函数放入任务队列中,等待当前执行栈清空。如果当前执行栈中有其他任务,那么即使设置为0毫秒,回调函数的执行仍然会被推迟。
JS中的计时器能做到精确计时吗?为什么?
参考:
2 .操作系统的计时函数本身就有少量偏差由于 JS 的计时器最终调用的是操作系统的函数,也就携带了这些偏差
3.按照 W3C 的标准,浏览器实现计时器时,如果嵌套层级超过 5 层,则会带有 4 毫秒的最少时间,这样在计时时间少于4毫秒时又带来了偏差
- 受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此又带来了偏差
浏览器新开一个窗口?—>进程
进程是CPU资源分配的最小单位;线程是CPU调度的最小单位。
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
JS单线程实现复杂化
所以浏览器采用异步的方式来避免。具体做法是当某些任务发生时,比如计时器、网络、事件监听,主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行。在这种异步模式下,浏览器永不阻塞,从而最大限度的保证了单线程的流畅运行。
定义:
A.主线程循环不断的从任务队列中读取事件(任务),这整个过程就称为Event Loop(事件循环)。
/B. JavaScript 的事件循环是一种机制,用于处理异步任务,通过不断循环执行任务队列中的事件,确保非阻塞的单线程代码执行顺序。
- 所有任务可以分成两种:
- 异步执行的运行机制:(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
- 所有都在上执行,形成一个(execution context stack)。
- 主线程之外,还存在一个(task queue)。只要有了运行结果,就在任务队列之中放置一个事件。
- 一旦"执行栈"中的,系统就会读取任务队列,看看里面有哪些事件。那些对应的,于是结束等待状态,。
- 主线程不断重复上面的第三步。
堆、执行栈、队列图:
事件循环机制的核心就是观察者模式。程序执行的流程:
- js程序进入线程,函数入栈,当遇到同步代码的时候就顺序执行,遇到异步代码时,把异步任务抛给WebAPIs执行,然后继续执行接下来的同步代码,直到栈为空。
- 在步骤1进行的同时,WebAPIs执行异步任务,当执行完一个异步任务就将其对应的回调函数放入任务队列(Callback Queue)中等待。
WebAPIs是由C++实现的浏览器创建的线程,处理诸如DOM事件,http请求,定时器等异步任务。 - 当执行栈为空时,从Callback Queue中取出队列头放入执行栈中,回到第一步。
当执行完一个异步任务就将其对应的回调函数放入任务队列(Callback Queue)中。
?但我们是如何判断这个异步任务执行完了呢——观察者模式。任务队列是观察者,WebAPIs是被观察者,观察者要求被观察者当发生执行完异步任务这一事件时,通知他执行完了,并将该事件对应的回调函数传过来。
js事件循环机制 优缺点与多线程的比较:
通过事件循环机制,我们就可以实现代码的异步,从而不会堵塞线程。
通过这一特性,
js在IO上有着卓越的表现,因为IO操作不再会堵塞住线程。
可以做到高并发。稍微解释一下为什么能够高并发——当同时有多个任务要执行,js将他一一排列起来,然后按顺序执行,这样cpu就不会因为同时要处理的工作太多而负载过大。
白银时代:多线程。黄金时代:事件驱动。不过我不敢说事件驱动就是比多线程好,但他确实没有多线程的这些恼人的缺陷。
如果有大量的线程,会影响性能,因为操作系统需要在线程之间不停进行上下文切换。
通常数据是多个线程共享的,需要上锁,同时又要防止出现死锁现象。
IO会堵塞住一个线程。
但同时的,js也有他的缺陷。
不适合cpu密集型。也解释一下——如一段代码需要非常大量的计算量,以至于他长时间地占着线程,这就堵塞了,后继的同步代码及异步代码都无法执行。不过,html5推出了web worker,可以有效地解决这一缺陷,在本章不表,后面我会专门写一篇文章来讲他。
只能使用一个线程,无法充分利用计算机的多核cpu。
可靠性低,一旦一个环节崩溃则整个程序全部崩溃。
事件循环是计算机的一种运行机制,不同技术在具体实现和调度机制上有所不同。浏览器与 Node 的事件循环差异有:
宏任务与微任务的执行顺序
浏览器:执行 1 个宏任务 -> 处理所有的微任务 -> 更新渲染 -> 继续下一轮宏任务。
Node:6 个宏任务队列 + 6 个微任务队列组成一次迭代。
在一个宏任务队列全部执行完毕后,去清空一次微任务队列,然后到下一个等级的宏任务队列,以此往复。六个等级的宏任务全部执行完成,才是一轮循环。
另外 Node 不同版本的事件循环机制也有差别,在讨论时应先指定版本。随着 Node 的更新,其事件循环大体上有与浏览器靠拢的趋势。
JavaScript的事件分两种 ------宏任务(macro-task)和微任务(micro-task):
宏任务包含:
ps:宏任务指的是计划由标准机制来执行的任何 JavaScript 代码,例如一段同步代码、一个用户事件、一个定时器的回调函数或一次 I/O 操作。在一些地方,“任务”指的就是宏任务。
setTimeOut执行需要满足两个条件:
主进程必须是空闲的状态,如果到时间了,主进程不空闲也不会执行你的回调函数
这个回调函数需要等到插入异步队列时前面的异步函数都执行完了,才会执行
注意: setTimeOut并不是直接的把回调函数放进异步队列中,而是在定时器的时间到了之后,把回调函数放到执行异步队列中去。如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么setTimeOut为什么不能精准的执行的问题了。
微任务包含:
Event Loop中执行一个宏任务(栈中没有就从事件队列中获取)执行过程中如果遇到微任务,就将它添加到微任务的任务队列中,宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行),然后开始下一个宏任务(从事件队列中获取)
promise、async/await
- 是的任务,会被放到中去立即执行。
- 函数是异步任务会放到中去,具体就是当promise状态结束(fulfilled)的时候,会立即放进异步队列中去。
- 关键字的函数会返回一个promise对象,如果里面没有await,执行起来等同于普通函数。
- 关键字要在 async 关键字函数的内部,await 写在外面会报错。await如同他的语意,就是在等待,等待右侧的表达式完成。
- 会让出线程,阻塞async内后续的代码,先去执行async外的代码。等外面的同步代码执行完毕,才会执行里面的后续代码。就算await的不是promise对象,是一个同步函数,也会同样执行。
其他异步任务:
requestAnimationFrame(callback) - RAF 仅仅存在于浏览器环境,执行时机在 setTimeout 之前执行
queueMicrotask:执行时机在第一轮同步任务执行完成之后,微任务执行之前。
执行顺序(总结)+练习:
1.宏任务(从上到下、从左到右的整体)
2.微任务的Event Queue(Promise.then,async / await整体,process.nextTick【node环境】)
3.宏任务的Event Queue(setTimeout / setInterval / setImmediate【node环境】)
4.同一轮中,依次顺序执行 process.nextTick 、queueMicrotask、Promise.then和async/await
5.同一轮中,setImmediate 在 setTimeout 之后执行
6.在同一轮任务队列中,requestAnimationFrame 在 setTimeout 之前执行
- 在宏任务执行过程中,执行到微任务时,将微任务放入微任务队列中。
练习1:
第 1 次迭代:
- 没有同步任务,微任务 Promise 1 的 放入等待执行。
- 放入任务队列。
3. 执行微任务队列:打印Promise 1、把 放入任务队列。微任务队列清空,本次迭代结束。
第 2 次迭代 Timeout 1:
- 同步任务:打印Timeout 1。
- 微任务 Promise 2 的放入微任务队列。
- 同步任务结束,执行微任务队列,即打印 Promise 2。微任务队列清空,本次迭代结束。
第 3 次迭代 Timeout 2:
- 打印Timeout 2=。
—done
结果为:Promise 1 -> Timeout 1 -> Promise 2 -> Timeout 2
练习2:
第 1 次迭代:
- 同步代码打印 1;
- 宏任务 放入任务队列;
- 同步代码打印 3;
- 同步代码务 new Promise() 打印 4、5;
- 微任务 Promise 的 放入微任务队列;
- 同步代码打印== 7==;
- 同步代码结束,执行:打印 6=。微任务队列清空,本次迭代结束。
第 2 次迭代 Timeout 1:
- 执行 的回调:打印== 2==。
—done
结果为:1 -> 3 -> 4 -> 5 -> 7 -> 6 -> 2
练习3:await:
解析:在函数中, 后的内容会被作为微任务放入微任务队列等待执行。函数 foo 等价于:
结果:甲 1 -> 甲 2 -> 乙 1 -> 乙 2 -> 甲 3 -> 乙 3
练习4:
第 1 次迭代:
- 同步代码代码打印1;(foo()执行放在此前,则先打印8->1->4…)
- 执行: 同步打印 8,的 9 放入微任务队列;
3. 回调放入任务队列; - 同步代码 new Promise 打印 4、5,回调 放入微任务队列;
- 同步代码代码打印 7;
- 同步代码结束,执行:打印 9、6;
第 2 次迭代:
- 回调打印2。
—done
结果:1 -> 8 -> 4 -> 5 -> 7 -> 9 -> 6 -> 2
什么时候会发生回流和重绘呢:
当页面的某部分元素发生了尺寸、位置、隐藏发生了改变,页面进行回流。得对整个页面重新进行布局计算,将所有尺寸,位置受到影响的元素回流。 - 当页面的某部分元素的外观发生了改变,但尺寸、位置、隐藏没有改变,页面进行重绘。(同样,只重绘部分元素,而不是整个页面重绘)
回流的同时往往会伴随着重绘,重绘不一定导致回流。所以回流导致的代价是大于重绘的。
频繁的回流与重绘会导致频繁的页面渲染,导致cpu或gpu过量使用,使得页面卡顿。
那么如何减少回流呢:
- 减少逐项更改样式,最好一次性更改style,或是将更改的样式定义在class中并一次性更新
- 避免循环操作DOM,而是新建一个节点,在他上面应用所有DOM操作,然后再将他接入到DOM中
- 当要频繁得到如offset属性时,只读取一次然后赋值给变量,而不是每次都获取一次
- 将复杂的元素绝对定位或固定定位,使他脱离文档流,否则回流代价很高 5. 使用硬件加速创建一个新的复合图层,当其需要回流时不会影响原始复合图层回流
练习5: 修改样式
执行以下代码,页面先变红还是先打印 End?
答案:先打印 End 再变红。
这 3 句代码都是同步任务。但是,浏览器会在当前宏任务、微任务队列执行完毕后,再重绘页面,因此是先打印 End 再变红。
一些地方说“修改页面样式是一个宏任务”,这是错误的。可以用下面代码验证:
输出:rgb(255, 0, 0) -> Timeout。
如果修改样式是宏任务,那么就会被排在 setTimeout 之后,那样打印 bgColor 就不会是红色,而是修改前的颜色。这与事实不符,可见修改页面样式不是宏任务,而是同步代码,只是因为浏览器会在本次迭代的最后来渲染页面,所以修改效果会在所有同步代码结束之后。
是es6引入的异步编程的新解决方案。语法上promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
Promise.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
解决问题: 层层嵌套的回调函数
状态: pending fulfilled rejected, 状态一旦改变就不会再变。
缺点:
- promise一旦新建就会立即执行,无法中途取消;
- 当处于pending状态时,无法得知当前处于哪一个状态,是刚刚开始还是刚刚结束;
- 如果不设置回调函数,promise内部的错误就无法反映到外部;
- promise封装ajax时,由于promise是异步任务,发送请求的三步会被延后到整个脚本同步代码执行完,并且将响应回调函数延迟到现有队列的最后,如果大量使用会大大降低了请求效率。
实例化promise对象:
封装读取文件
封装Ajax请求
Promise.prototype.then
手写promise
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/bcyy/77412.html