I believe everyone who has studied JavaScript knows that it is a single-threaded language, which means that JS cannot perform multi-threaded programming, but there are ubiquitous in JS Asynchronous concept. In the early days, many people would understand asynchronous as a programming model similar to multi-threading. In fact, there are big differences between them. To fully understand asynchronous, you need to understand the running core of JS - Event loop (event loop). I had only a limited understanding of the event loop before. It wasn’t until I watched Philip Roberts’s speech What the heck is the event loop anyway? that I had a comprehensive understanding of the event loop, so I wanted to write an introduction to JS The event loop article is for everyone’s learning and reference.
Why is there asynchrony in JS? Let's imagine what will happen if we execute the code synchronously:
1 $.get(url, function(data) {2 //do something3 });
When we use ajax to communicate, we all default to it being asynchronous, but if we set It's executed synchronously, what happens? If you write a small test program yourself and delay the background code for 5 seconds, you will find that the browser will block until ajax responds and then run normally. This is the primary problem that the asynchronous mode needs to solve, how to make the browser run tasks non-blockingly. Imagine if we execute the ajax request synchronously, our waiting time is an unknown quantity. It may be very fast or very slow in network communication, or it may never respond. This will also cause the browser to block. On an unknown mission, this is what we don't want to see. So we hope there is a way to handle the program asynchronously. We don't need to care about when an ajax request will be completed, or even it may never respond. We only need to know how to handle the request after it responds, and wait for the response. We can also do some other work during this time. Hence, JavaScript Event Loop.
First, let’s look at a simple piece of code:
1 console.log("script start");2 3 setTimeout(function () {4 console.log("setTimeout");5 }, 1000);6 7 console.log("script end");
You can view the results here:
We can see that, first of all, The program outputs 'script start' and 'script end', and 'setTimeout' is output after about 1s. The program's 'script end' does not wait for 1s to output, but outputs immediately. This is because setTimeout is an asynchronous function. This means that when we set a delay function, the current script will not block, it will just be recorded in the browser's event table, and the program will continue to execute. When the delay time is over, the event table will add the callback function to the event queue (task queue) . After the event queue gets the task, it will push the task into the execution stack (stack) Among them, the execution stack executes the task and outputs 'setTimeout'.
The event queue is a queue that stores tasks to be executed. The tasks are executed strictly in chronological order. The tasks at the head of the queue will be executed first, and the tasks at the end of the queue will be executed last. The event queue only executes one task at a time. After the task is completed, the next task is executed. The execution stack is a running container similar to a function call stack. When the execution stack is empty, the JS engine checks the event queue. If it is not empty, the event queue pushes the first task into the execution stack for running.
Now, let’s make a little modification to the above code:
1 console.log("script start");2 3 setTimeout(function () {4 console.log("setTimeout");5 }, 0);6 7 console.log("script end");
Set the delay time to 0 and see in what order the program will output? No matter how much delay time we set, 'setTimeout' will always be output after 'script end'. Some browsers may have a minimum delay time, some are 15ms, some are 10ms, this is mentioned in many books, which may give students an illusion: because the program runs very fast and has a minimum delay time , so 'setTimeout' will be output after 'script end'. Now let’s change it a little bit to dispel your illusion:
1 console.log("script start"); 2 3 setTimeout(function () { 4 console.log("setTimeout"); 5 }, 0); 6 7 //具体数字不定,这取决于你的硬件配置和浏览器 8 for(var i = 0; i < 999999999; i ++){ 9 //do something10 }11 12 console.log("script end");
你可以在这里查看结果:
可以看出,无论后面我们做了多少延迟性的工作,'setTimeout' 总是会在 'script end' 之后输出。所以究竟发生了什么?这是因为 setTimeout 的回调函数只是会被添加至事件队列,而不是立即执行。由于当前的任务没有执行结束,所以 setTimeout 任务不会执行,直到输出了 'script end' 之后,当前任务执行完毕,执行栈为空,这时事件队列才会把 setTimeout 回调函数压入执行栈执行。
执行栈则像是函数的调用栈,是一个树状的栈:
通过以上的 demo 相信同学们都会对事件队列和执行栈有了一个基本的认识,那么事件队列有何作用?最简单易懂的一点就是之前我们所提到的异步问题。由于 JS 是单线程的,同步执行任务会造成浏览器的阻塞,所以我们将 JS 分成一个又一个的任务,通过不停的循环来执行事件队列中的任务。这就使得当我们挂起某一个任务的时候可以去做一些其他的事情,而不需要等待这个任务执行完毕。所以事件循环的运行机制大致分为以下步骤:
检查事件队列是否为空,如果为空,则继续检查;如不为空,则执行 2;
取出事件队列的首部,压入执行栈;
执行任务;
检查执行栈,如果执行栈为空,则跳回第 1 步;如不为空,则继续检查;
然而目前为止我们讨论的仅仅是 JS 引擎如何执行 JS 代码,现在我们结合 Web APIs 来讨论事件循环在当中扮演的角色。
在开始我们讨论过 ajax 技术的异步性和同步性,通过事件循环机制,我们则不需要等待 ajax 响应之后再进行工作。我们则是设置一个回调函数,将 ajax 请求挂起,然后继续执行后面的代码,至于请求何时响应,对我们的程序不会有影响,甚至它可能永远也不响应,也不会使浏览器阻塞。而当响应成功了以后,浏览器的事件表则会将回调函数添加至事件队列中等待执行。事件监听器的回调函数也是一个任务,当我们注册了一个事件监听器时,浏览器事件表会进行登记,当我们触发事件时,事件表便将回调函数添加至事件队列当中。
我们知道 DOM 操作会触发浏览器对文档进行渲染,如修改排版规则,修改背景颜色等等,那么这类操作是如何在浏览器当中奏效的?至此我们已经知道了事件循环是如何执行的,事件循环器会不停的检查事件队列,如果不为空,则取出队首压入执行栈执行。当一个任务执行完毕之后,事件循环器又会继续不停的检查事件队列,不过在这间,浏览器会对页面进行渲染。这就保证了用户在浏览页面的时候不会出现页面阻塞的情况,这也使 JS 动画成为可能, jQuery 动画在底层均是使用 setTimeout 和 setInterval 来进行实现。想象一下如果我们同步的执行动画,那么我们不会看见任何渐变的效果,浏览器会在任务执行结束之后渲染窗口。反之我们使用异步的方法,浏览器会在每一个任务执行结束之后渲染窗口,这样我们就能看见动画的渐变效果了。
考虑如下两种遍历方式:
1 var arr = new Array(999); 2 arr.fill(1); 3 function asyncForEach(array, handler){ 4 var t = setInterval(function () { 5 if(array.length === 0){ 6 clearInterval(t); 7 }else { 8 handler(arr.shift()); 9 }10 }, 0);11 }12 13 //异步遍历14 asyncForEach(arr, function (value) {15 console.log(value);16 });17 18 //同步遍历19 arr.forEach(function (value, index, arr) {20 console.log(value);21 });
After testing, we can see that using the synchronous traversal method, when the array length rises to 3 digits, blocking will occur, but asynchronous traversal will not block (unless the array length is very large, then This is because the computer does not have enough memory space). This is because the synchronous traversal method is a separate task. This task will traverse all array elements before starting the next task. The asynchronous traversal method splits each traversal into a separate task. Each task only traverses one array element, so between each task, our browser can render, so we will not see blocking. The following demo demonstrates what happens before and after asynchronous traversal:
Now, I believe you have understood the true face of JavaScript. JavaScript is a single-threaded language, but its event loop feature allows us to execute programs asynchronously. These asynchronous programs are independent tasks one after another. These tasks include setTimeout, setInterval, ajax, eventListener, etc. Regarding the event loop, we need to remember the following points:
The event queue pushes tasks into the execution stack in strict chronological order;
When the execution stack is empty, the browser will continue to check the event queue. If it is not empty, the first task will be taken out;
After each task ends, The browser will render the page;
The demo of this article is placed on jsfiddle. If you need to reprint it, just indicate the source. If you find any mistakes in this article, please point them out in the comment area.
The above is the detailed content of Why is there asynchrony? What is an event queue?. For more information, please follow other related articles on the PHP Chinese website!