Vous donner une compréhension approfondie du mécanisme de boucle d'événement js

Tâches synchrones et tâches asynchrones (microtâches et macrotâches)

JavaScript est un langage monothread

Divisé en tâches synchrones et tâches asynchrones

Une tâche synchrone fait référence à une tâche mise en file d'attente pour exécution sur le thread principal. Ce n'est que lorsque la tâche précédente est terminée que la tâche suivante peut être exécutée.

Les tâches asynchrones font référence aux tâches qui n'entrent pas dans le thread principal mais entrent dans la "file d'attente des tâches" ; seulement après que les tâches du thread principal sont toutes exécutées, les tâches de la "file d'attente des tâches" entreront dans le thread principal pour exécution.

Les tâches asynchrones sont divisées en macro tâches et micro tâches

new promise(), console.log() sont des tâches synchrones

Tâche macro (macrotask) microtâche
qui a initié Hôte (nœud, navigateur) Moteur JS
incident spécifique 1. script (peut être compris comme code de synchronisation externe) 2. setTimeout/setInterval 3. Rendu UI/événement UI 4. postMessage, MessageChannel 5. setImmediate, I/O (Node.js) 1. Promise 2. MutaionObserver 3. Object.observe (obsolète ; remplacement d'objet proxy) 4. process.nextTick (Node.js)
qui court en premier après la course courir en premier
Une nouvelle série de Tick sera-t-elle déclenchée ? réunion Ne le fera pas

Processus d'exécution : tâche synchrone —> micro tâche —> macro tâche

1. Exécutez d'abord toutes les tâches synchrones et placez les tâches asynchrones dans la file d'attente des tâches
2. Une fois la tâche synchrone exécutée, commencez à exécuter toutes les tâches asynchrones en cours
3. Exécutez d'abord toutes les micro-tâches dans la file d'attente des tâches
4. Ensuite, exécutez une tâche macro
5. Exécutez ensuite toutes les microtâches
6. Exécutez à nouveau une macrotâche, puis exécutez toutes les microtâches... et ainsi de suite jusqu'à la fin de l'exécution.

Cette boucle de 3 à 6 s'appelle la boucle d'événement Boucle d'événement

La boucle d'événements est une méthode permettant à JavaScript d'obtenir l'asynchronisme, et c'est également le mécanisme d'exécution de JavaScript

asynchrone/attente (emphase)

(Remarque personnelle : la couche inférieure de async/wait est toujours Promise, c'est donc une microtâche, mais l'attente est spéciale)

asynchrone

Lorsque nous utilisons async avant une fonction, la fonction renvoie un objet Promise

async function test() {return 1 // async的函数会在这里帮我们隐士使用Promise.resolve(1)
}
// 等价于下面的代码
function test() { return new Promise(function(resolve, reject) { resolve(1) })
}
// 可见async只是一个语法糖,只是帮助我们返回一个Promise而已 

attendre

wait signifie attendre, et c'est le résultat de "l'expression" à droite. Le résultat de cette expression peut être la valeur d'un objet Promise ou d'une fonction (en d'autres termes, il n'y a pas de restrictions particulières). et ne peut être utilisé qu'à l'intérieur avec async

Lorsqu'attend est utilisé, il sera exécuté de droite à gauche. Lorsqu'attend attend, ★★★★★ bloquera le code derrière lui à l'intérieur de la fonction pour exécuter le code de synchronisation en dehors de la fonction. Lorsque le code de synchronisation externe est exécuté, il renverra Exécuter le reste du code à l'intérieur de la fonction★★★★★, et lorsque l'exécution en attente est terminée, le code de la file d'attente des microtâches sera traité en premier

exemple

//1
console.log('1');//2
setTimeout(function() {console.log('2');process.nextTick(function() {console.log('3');})new Promise(function(resolve) {console.log('4');resolve();}).then(function() {console.log('5')})
})
//3
process.nextTick(function() {console.log('6');
})
//4
new Promise(function(resolve) {console.log('7');resolve();
}).then(function() {console.log('8')
})
//5
setTimeout(function() {console.log('9');process.nextTick(function() {console.log('10');})new Promise(function(resolve) {console.log('11');resolve();}).then(function() {console.log('12')})
})

// 先执行1 输出1
// 执行到2,把setTimeout放入异步的任务队列中(宏任务)
// 执行到3,把process.nextTick放入异步任务队列中(微任务)
// 执行到4,上面提到promise里面是同步任务,所以输出7,再将then放入异步任务队列中(微任务)
// 执行到5,同2
// 上面的同步任务全部完成,开始进行异步任务
// 先执行微任务,发现里面有两个微任务,分别是3,4压入的,所以输出6 8
// 再执行一个宏任务,也就是第一个setTimeout
// 先输出2,把process.nextTick放入微任务中,再如上promise先输出4,再将then放入微任务中
// 再执行所以微任务输出输出3 5
// 同样的,再执行一个宏任务setTImeout2,输出9 11 在执行微任务输出10 12
// 所以最好的顺序为:1 7 6 8 2 4 3 5 9 11 10 12 
async function async1() {console.log( 'async1 start' )await async2()console.log( 'async1 end' )
}

async function async2() {console.log( 'async2' )
}
console.log( 'script start' )
setTimeout( function () {console.log( 'setTimeout' )
}, 0 )
async1();
new Promise( function ( resolve ) {console.log( 'promise1' )resolve();
} ).then( function () {console.log( 'promise2' )
} )
console.log( 'script end' )

// 首先执行同步代码,console.log( 'script start' )
// 遇到setTimeout,会被推入宏任务队列
// 执行async1(), 它也是同步的,只是返回值是Promise,在内部首先执行console.log( 'async1 start' )
// 然后执行async2(), 然后会打印console.log( 'async2' )
// 从右到左会执行, 当遇到await的时候,阻塞后面的代码,去外部执行同步代码
// 进入new Promise,打印console.log( 'promise1' )
// 将.then放入事件循环的微任务队列
// 继续执行,打印console.log( 'script end' )
// 外部同步代码执行完毕,接着回到async1()内部, 继续执行 await async2() 后面的代码,执行 console.log( 'async1 end' ) ,所以打印出 async1 end 。(个人理解:async/await本质上也是Promise,也是属于微任务的,所以当遇到await的时候,await后面的代码被阻塞了,应该也是被放到微任务队列了,当同步代码执行完毕之后,然后去执行微任务队列的代码,执行微任务队列的代码的时候,也是按照被压入微任务队列的顺序执行的)
// 执行微任务队列的代码, 打印 console.log( 'promise2' )
// 进入第二次事件循环,执行宏任务队列, 打印console.log( 'setTimeout' )
/**
 * 执行结果为:
 * script start
 * async1 start
 * async2
 * promise1
 * script end
 * async1 end
 * promise2
 * setTimeout
 */ 
console.log(1);
async function fn(){console.log(2)new Promise((resolve)=>{resolve();}).then(()=>{console.log("XXX")})await console.log(3)console.log(4)
}
fn();
new Promise((resolve)=>{console.log(6)resolve();
}).then(()=>{console.log(7)
})
console.log(8)

// 执行结果为:1 2 3 6 8 XXX 4 7
/*
前面的 1 2 3 6 8 不再解析,重点是后面的 XXX 4 7,由此可见 await console.log(3) 之后的代码 console.log(4) 是被放入到微任务队列了,
代码 console.log("XXX") 也是被压入微任务队列了,console.log("XXX")是在 console.log(4) 之前,
所以当同步任务执行完毕之后,执行微任务队列代码的时候,优先打印出来的是 XXX ,然后才是 4 。
*/ 
console.log(1);
async function fn(){console.log(2)await console.log(3)await console.log(4)await console.log("await之后的:",11)await console.log("await之后的:",22)await console.log("await之后的:",33)await console.log("await之后的:",44)
}
setTimeout(()=>{console.log(5)
},0)
fn();
new Promise((resolve)=>{console.log(6)resolve();
}).then(()=>{console.log(7)
})
console.log(8)

/**
 * 执行结果为:
 * 1
 * 2
 * 3
 * 6
 * 8
 * 4
 * 7
 * await之后的: 11
 * await之后的: 22
 * await之后的: 33
 * await之后的: 44
 * 5
 */
/*
由此可见,代码执行的时候,只要碰见 await ,都会执行完当前的 await 之后,
把 await 后面的代码放到微任务队列里面。但是定时器里面的 5 是最后打印出来的,
可见当不断碰见 await ,把 await 之后的代码不断的放到微任务队列里面的时候,
代码执行顺序是会把微任务队列执行完毕,才会去执行宏任务队列里面的代码。
*/ 
Promise.resolve().then(() => {console.log(0);return Promise.resolve(4) // 顺延2位如果是return 4 则打印 0、1、4、2、3、5、6、7
}).then(res => console.log(res))
 
Promise.resolve().then(() => {console.log(1);
}).then(() => {console.log(2);
}).then(() => {console.log(3);
}).then(() => {console.log(5);
}).then(() => {console.log(6);
}).then(() => {console.log(7);
})
/*
此题主要注意的是原生的Promise的then方法中,如果返回的是一个普通值,则返回的值会被立即调用并赋值给resolve函数,
如果返回的是一个thenable,则then方法将会被放入到微队列中执行,
如果返回的是一个Promise.resolve,则会再加一次微任务队列。
即微任务后移,Promise.resolve本身是执行then方法,而then方法本身是在微任务队列中执行,
同时return Promise.resolve时是将resolve调用的返回值 作为上级then中resolve的参数传递,
调用外层then方法时本身是在微队列里面,所以函数的执行顺序是要在微队列中下移两次。

*/ 

Selon la dernière explication du w3c

  • Chaque tâche a un type de tâche. Les tâches du même type doivent se trouver dans une file d'attente, c'est-à-dire qu'il existe plusieurs files d'attente. Différents types de tâches peuvent appartenir à différentes files d'attente. Dans un cycle de sous-événements, le navigateur peut différencier l'exécution des tâches de différentes files d'attente
  • Le navigateur doit préparer une micro-file d'attente. Les tâches de la micro-file d'attente ont la priorité et toutes les autres tâches exécutent les choses qu'elle contient. Tous doivent m'attendre. Même la tâche de dessin doit attendre, ce qui est la priorité la plus élevée.

Le W3C n'utilise plus les files d'attente de macros alors que la complexité du navigateur monte en flèche

Au moins les files d'attente suivantes sont incluses dans l'implémentation actuelle de Chrome

  • File d'attente différée : utilisée pour stocker les tâches de rappel après l'arrivée du minuteur, avec priorité
  • File d'attente interactive : utilisée pour stocker les tâches de traitement des événements générées après les opérations de l'utilisateur, avec une priorité élevée
  • Micro-file d'attente : l'utilisateur stocke les tâches nécessitant l'exécution la plus rapide avec la priorité la plus élevée

La principale façon d'ajouter des tâches à la microfile d'attente est d'utiliser Promise et MutationObserver

例如:
// 立即把一个函数添加到微队列
Promise.resolve().then(函数) 

Les tâches sont-elles priorisées ?

  • Les tâches n'ont pas de priorité, premier entré, premier sorti dans la file d'attente des messages
  • Mais la file d'attente des messages a la priorité
// 立刻把一个函数添加到微队列 最高执行
promise.resolve().then(函数)

setTimeOut(()=>{ // 第三步执行延时队列中的任务console.log(1);
},0)

promise.resolve().then(()=>{ // 第二步执行微队列中的任务console.log(2);
})

console.log(3); // 第一步先执行全局js

// 3 2 1 

questions d'entretien

1. Comment comprendre l'asynchronisme du JS ?

JS est un langage à thread unique car il s'exécute sur le thread de rendu principal du navigateur et il n'y a qu'un seul thread principal de rendu.

Le thread principal de rendu entreprend de nombreuses tâches, le rendu des pages et l'exécution de JS y sont tous exécutés.

Si vous utilisez une méthode synchrone, il est très probable que le thread principal soit bloqué, ce qui empêchera l'exécution de nombreuses autres tâches dans la file d'attente des messages. De cette façon, d'une part, le thread principal occupé perdra du temps en vain, et d'autre part, la page ne pourra pas être mise à jour à temps, ce qui bloquera l'utilisateur.

Le navigateur utilise donc un moyen asynchrone pour l'éviter. La méthode spécifique est que lorsque certaines tâches se produisent, telles que les minuteries, les réseaux et la surveillance des événements, le thread principal transmettra les tâches à d'autres threads pour traitement, et mettra immédiatement fin à l'exécution des tâches par lui-même, puis exécutera les codes suivants. . Lorsque les autres threads sont terminés, encapsulez la fonction de rappel transmise à l'avance dans une tâche, ajoutez-la à la fin de la file d'attente des messages et attendez que le thread principal planifie l'exécution.

Dans ce mode asynchrone, le navigateur ne se bloque jamais, garantissant ainsi au maximum le bon fonctionnement du thread unique.

2. Expliquez la boucle d'événements de js

La boucle d'événements, également connue sous le nom de boucle de messages, est la façon dont le navigateur restitue le thread principal.

Dans le code source de Chrome, il démarre une boucle for sans fin, chaque boucle récupère la première tâche de la file d'attente de messages pour l'exécution, et les autres threads n'ont qu'à ajouter la tâche à la fin de la file d'attente au moment approprié.

Dans le passé, les files d'attente de messages étaient simplement divisées en files d'attente de macros et en micro files d'attente. Cette instruction ne peut plus satisfaire l'environnement complexe du navigateur et une méthode de traitement plus flexible et modifiable a été remplacée.

Selon l'explication officielle du W3C, chaque tâche a un type différent, les tâches du même type doivent être dans la même file d'attente et différentes tâches peuvent appartenir à différentes files d'attente. Différentes files d'attente de tâches ont des priorités différentes. Dans une boucle d'événements, le navigateur décide quelle file d'attente prendre. Mais le navigateur doit avoir une micro-file d'attente, et les tâches de la micro-file d'attente doivent avoir la priorité la plus élevée et doivent être planifiées et exécutées en premier.

3. La minuterie dans JS peut-elle atteindre un timing précis ? Pourquoi?

Non parce que:

1. Le matériel informatique n'a pas d'horloge atomique, il est donc impossible d'obtenir une synchronisation précise.
2. La fonction de synchronisation du système d'exploitation lui-même présente une petite déviation. Étant donné que la minuterie JS appelle finalement la fonction du système d'exploitation, elle 3. Selon la norme W3C, lorsque le navigateur implémente la minuterie, si le niveau d'imbrication dépasse
5 couches, il aura un temps minimum de 4 millisecondes, ce qui entraînera une déviation lorsque le temps de synchronisation est inférieur à 4 4. Affecté par la boucle d'événements,
la synchronisation La fonction de rappel du contrôleur ne peut s'exécuter que lorsque le thread principal est inactif, introduisant ainsi un biais

enfin

Organisation d'un ensemble de "Collection d'interviews de fabricants frontaux", comprenant HTML, CSS, JavaScript, HTTP, protocole TCP, navigateur, VUE, React, structure de données et algorithme, un total de 201 questions d'entretien, et réponse à chacune question Répondez et analysez.

Amis dans le besoin, vous pouvez cliquer sur la carte en fin d'article pour recevoir ce document et le partager gratuitement

Une partie de la documentation montre :



La longueur de l'article est limitée, et les contenus suivants ne seront pas affichés un par un

Amis dans le besoin, vous pouvez cliquer sur la carte ci-dessous pour l'obtenir gratuitement

Je suppose que tu aimes

Origine blog.csdn.net/Android_boom/article/details/127678182
conseillé
Classement