当前位置:网站首页 > 编程语言 > 正文

重绘回流区别(回流重绘发生在哪个阶段)



定时执行代码的功能。

运行机制:

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毫秒时又带来了偏差

  1. 受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此又带来了偏差

浏览器新开一个窗口?—>进程
进程是CPU资源分配的最小单位;线程是CPU调度的最小单位。
在这里插入图片描述

在这里插入图片描述
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

JS单线程实现复杂化 在这里插入图片描述

所以浏览器采用异步的方式来避免。具体做法是当某些任务发生时,比如计时器、网络、事件监听,主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行。在这种异步模式下,浏览器永不阻塞,从而最大限度的保证了单线程的流畅运行。

定义:
A.主线程循环不断的从任务队列中读取事件(任务),这整个过程就称为Event Loop(事件循环)。
/B. JavaScript 的事件循环是一种机制,用于处理异步任务,通过不断循环执行任务队列中的事件,确保非阻塞的单线程代码执行顺序。

  • 所有任务可以分成两种:
  • 异步执行的运行机制:(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
    1. 所有都在上执行,形成一个(execution context stack)。
    2. 主线程之外,还存在一个(task queue)。只要有了运行结果,就在任务队列之中放置一个事件。
    3. 一旦"执行栈"中的,系统就会读取任务队列,看看里面有哪些事件。那些对应的,于是结束等待状态,。
    4. 主线程不断重复上面的第三步。

堆、执行栈、队列图:
在这里插入图片描述

事件循环机制的核心就是观察者模式。程序执行的流程:

  1. js程序进入线程,函数入栈,当遇到同步代码的时候就顺序执行,遇到异步代码时,把异步任务抛给WebAPIs执行,然后继续执行接下来的同步代码,直到栈为空。
  2. 在步骤1进行的同时,WebAPIs执行异步任务,当执行完一个异步任务就将其对应的回调函数放入任务队列(Callback Queue)中等待。
    WebAPIs是由C++实现的浏览器创建的线程,处理诸如DOM事件,http请求,定时器等异步任务。
  3. 当执行栈为空时,从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 次迭代:

  1. 没有同步任务,微任务 Promise 1 的 放入等待执行。
  2. 放入任务队列。
    3. 执行微任务队列:打印Promise 1、把 放入任务队列。微任务队列清空,本次迭代结束。

第 2 次迭代 Timeout 1:

  1. 同步任务:打印Timeout 1
  2. 微任务 Promise 2 的放入微任务队列。
  3. 同步任务结束,执行微任务队列,即打印 Promise 2。微任务队列清空,本次迭代结束。

第 3 次迭代 Timeout 2:

  1. 打印Timeout 2=。

—done

结果为:Promise 1 -> Timeout 1 -> Promise 2 -> Timeout 2

练习2:

 

第 1 次迭代:

  1. 同步代码打印 1
  2. 宏任务 放入任务队列;
  3. 同步代码打印 3
  4. 同步代码务 new Promise() 打印 4、5
  5. 微任务 Promise 的 放入微任务队列;
  6. 同步代码打印== 7==;
  7. 同步代码结束,执行:打印 6=。微任务队列清空,本次迭代结束。

第 2 次迭代 Timeout 1:

  1. 执行 的回调:打印== 2==。

—done

结果为:1 -> 3 -> 4 -> 5 -> 7 -> 6 -> 2

练习3:await

 

解析:在函数中, 后的内容会被作为微任务放入微任务队列等待执行。函数 foo 等价于:

 

结果:甲 1 -> 甲 2 -> 乙 1 -> 乙 2 -> 甲 3 -> 乙 3

练习4:

 

第 1 次迭代:

  1. 同步代码代码打印1;(foo()执行放在此前,则先打印8->1->4…)
  2. 执行: 同步打印 8,的 9 放入微任务队列;
    3. 回调放入任务队列;
  3. 同步代码 new Promise 打印 4、5,回调 放入微任务队列;
  4. 同步代码代码打印 7
  5. 同步代码结束,执行:打印 9、6

第 2 次迭代:

  1. 回调打印2

—done

结果:1 -> 8 -> 4 -> 5 -> 7 -> 9 -> 6 -> 2

什么时候会发生回流和重绘呢:

当页面的某部分元素发生了尺寸、位置、隐藏发生了改变,页面进行回流。得对整个页面重新进行布局计算,将所有尺寸,位置受到影响的元素回流。 - 当页面的某部分元素的外观发生了改变,但尺寸、位置、隐藏没有改变,页面进行重绘。(同样,只重绘部分元素,而不是整个页面重绘)
回流的同时往往会伴随着重绘,重绘不一定导致回流。所以回流导致的代价是大于重绘的。
频繁的回流与重绘会导致频繁的页面渲染,导致cpu或gpu过量使用,使得页面卡顿。

那么如何减少回流呢:

  1. 减少逐项更改样式,最好一次性更改style,或是将更改的样式定义在class中并一次性更新
  2. 避免循环操作DOM,而是新建一个节点,在他上面应用所有DOM操作,然后再将他接入到DOM中
  3. 当要频繁得到如offset属性时,只读取一次然后赋值给变量,而不是每次都获取一次
  4. 将复杂的元素绝对定位或固定定位,使他脱离文档流,否则回流代价很高 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
到此这篇重绘回流区别(回流重绘发生在哪个阶段)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • 打开目录命令(文件打开目录)2025-04-23 16:36:06
  • 华为机考题库2023材料类(华为机考2020)2025-04-23 16:36:06
  • ip域名提取查询 apk(ip域名批量提取查询app)2025-04-23 16:36:06
  • 爱普生c7000参数(爱普生 v700)2025-04-23 16:36:06
  • 结构游戏的分类ppt(结构游戏的分类及技巧有哪些)2025-04-23 16:36:06
  • 国内github(国内github手机app)2025-04-23 16:36:06
  • 2263xt 跳线(2258xt跳线)2025-04-23 16:36:06
  • 接口报错400(接口报错403常见原因)2025-04-23 16:36:06
  • 接口报403错误(接口状态403)2025-04-23 16:36:06
  • 创建autokeys宏(autokeys宏怎么建立的)2025-04-23 16:36:06
  • 全屏图片