了解 javascript 中的事件队列和调用堆栈 [英] Understanding Event Queue and Call stack in javascript

查看:25
本文介绍了了解 javascript 中的事件队列和调用堆栈的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对理解事件队列"和调用堆栈"概念的好奇心始于我解决这个问题时:

My curiosity for understanding the concept of "Event Queue" and "Call Stack" Started when I was solving this question:

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        nextListItem();
    }
};

如果数组列表太大,以下递归代码将导致堆栈溢出.您如何解决此问题并仍然保留递归模式?

提到的解决方案是这样的:

The solution that was mentioned was this:

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        setTimeout( nextListItem, 0);
    }
};

解决方案:

堆栈溢出被消除,因为事件循环处理递归,而不是调用堆栈.当 nextListItem 运行时,如果 item 不是null,超时函数(nextListItem)被推送到事件队列并且函数退出,从而清除调用堆栈.当...的时候事件队列运行它的超时事件,下一个项目被处理并且一个计时器设置为再次调用 nextListItem.因此,该方法是从头到尾处理,没有直接递归调用,所以无论迭代次数如何,调用堆栈都保持清晰.

The stack overflow is eliminated because the event loop handles the recursion, not the call stack. When nextListItem runs, if item is not null, the timeout function (nextListItem) is pushed to the event queue and the function exits, thereby leaving the call stack clear. When the event queue runs its timed-out event, the next item is processed and a timer is set to again invoke nextListItem. Accordingly, the method is processed from start to finish without a direct recursive call, so the call stack remains clear, regardless of the number of iterations.

现在我的问题:

Q1)事件队列"和调用栈"有什么区别

Q1) What is the difference between "Event Queue" and "call stack"

Q2) 我不明白答案.有人能详细解释一下吗?

Q2) I did not understand the answer. Can someone explain me in detail?

Q3) 当我在 javascript 中执行一个函数或调用一个变量或对象时.流程如何? 调用堆栈中有什么?(假设我执行 setTimeout .. 是进入调用堆栈还是事件队列?)

Q3) When I execute a function or call a variable or object in javascript. How does the flow go? What goes in call stack? (Let's say I do setTimeout.. Does it go to callstack or event queue?)

这些概念很不清楚.我用谷歌搜索,但大部分结果都不是我期望理解的.

These concepts are very unclear. I googled but most of the results aren't what I was expecting to understand.

请帮忙!

推荐答案

Answer 1 &3

事件队列和调用栈有很大的区别.事实上,他们几乎没有任何共同点.

Answer 1 & 3

There is a very big difference between event queue and call stack. In fact, they have almost nothing in common.

当您执行一个函数时,它使用的所有内容都被称为在堆栈上,这与您所指的调用堆栈相同.非常简化,它是功能执行的临​​时内存.或者换句话说

When you execute a function everything it uses is said to go on the stack, which is the same call stack you're referring to there. Very simplified, it's temporary memory for functional execution. Or in other words

function foo() {
  console.log("-> start [foo]");
  console.log("<- end   [foo]");
}

foo();

当它被调用时,它会被给予一个小沙盒供在堆栈上使用.当函数结束时,使用的临时内存将被擦除并可供其他事物使用.因此,所使用的资源(除非在某处提供给系统)只会在功能持续的时间内持续使用.

when called it would be given a little sandbox to play with on the stack. When the function ends, the temporary memory used would be wiped and made available to other things. So, the resources used (unless given somewhere to the system) will only last as long as the function lasts.

现在,如果你有嵌套函数

Now, if you have nested functions

function foo() {
  console.log("-> start [foo]");
  console.log("<- end   [foo]");
}

function bar() {
  console.log("-> start [bar]");
  foo()
  console.log("<- end   [bar]");
}

bar();

这是调用函数时发生的情况:

Here is what happens when you call the function:

  1. bar 被执行 - 在堆栈上为其分配内存.
  2. bar 打印开始"
  3. foo 被执行 - 在堆栈上为其分配内存.注意! bar 仍在运行,它的内存也在那里.
  4. foo 打印开始"
  5. foo 打印结束"
  6. foo 完成执行,其内存从堆栈中清除.
  7. bar 打印结束"
  8. bar 完成执行,其内存从堆栈中清除.
  1. bar gets executed - memory is allocated on the stack for it.
  2. bar prints "start"
  3. foo gets executed - memory is allocated on the stack for it. NB! bar is still running and its memory is also there.
  4. foo prints "start"
  5. foo prints "end"
  6. foo finishes execution and its memory is cleared from the stack.
  7. bar prints "end"
  8. bar finishes execution and its memory is cleared from the stack.

所以,执行顺序是 bar -> foo 但解析是后进先出顺序(LIFO)foo 完成-> bar 完成.

So, the execution order is bar -> foo but the resolution is in last in, first out order (LIFO) foo finishes -> bar finishes.

这就是使它成为堆栈"的原因.

And this is what makes it a "stack".

这里需要注意的重要一点是,函数使用的资源只有在它执行完毕后才会释放.当它里面的所有函数和里面的函数都执行完时,它就完成了执行.所以你可以有一个非常深的调用堆栈,比如 a -> b -> c -> d ->e 并且如果 a 中包含任何大型资源,则需要 be 才能在它们被释放之前完成.

The important thing to note here is that the resources used by a function will only be released when it finishes executing. And it finishes execution when all the functions inside it and those inside them finish executing. So you could have a very deep call stack like a -> b -> c -> d -> e and if any large resources are held in a you would need b to e to finish before they are released.

在递归中,一个函数调用自身,它仍然在堆栈上创建条目.所以,如果 a 不断调用自己,你最终会得到一个 a -> a -> a 的调用堆栈-> a

In recursion a function calls itself which still makes entries on the stack. So, if a keeps calling itself you end up with a call stack of a -> a -> a -> a etc.

这是一个非常简短的说明

Here is a very brief illustration

// a very naive recursive count down function
function recursiveCountDown(count) {
  //show that we started
  console.log("-> start recursiveCountDown [" + count + "]");
  
  if (count !== 0) {//exit condition
    //take one off the count and recursively call again
    recursiveCountDown(count -1);
    console.log("<- end recursiveCountDown [" + count + "]"); // show where we stopped. This will terminate this stack but only after the line above finished executing;
  } else {
    console.log("<<<- it's the final recursiveCountDown! [" + count + "]"); // show where we stopped
  }
}

console.log("--shallow call stack--")
recursiveCountDown(2);

console.log("--deep call stack--")
recursiveCountDown(10);

这是一个非常简单且非常有缺陷的递归函数,但它仅用于演示在这种情况下会发生什么.

This is a very simplistic and very flawed recursive function but it only serves to demonstrate what happens in that case.

JavaScript 在事件队列(或事件循环")中运行,简单来说,它等待活动"(事件),处理它们,然后再次等待.

JavaScript is ran in an event queue (or also "event loop") which, in simple terms, waits for "activity" (events), processes them and then waits again.

如果有多个事件,它会按顺序处理它们 - 先进先出 (FIFO),因此是一个队列.所以,如果我们重写上面的函数:

If there are multiple events, it would process them in order - first in, first out (FIFO), hence a queue. So, if we re-write the above functions:

function foo() {
  console.log("-> start [foo]");
  console.log("<- end   [foo]");
}

function bar() {
  console.log("-> start [bar]");
  console.log("<- end   [bar]");
}


function baz() {
  console.log("-> start [baz]");
  
  setTimeout(foo, 0);
  setTimeout(bar, 0);
  
  console.log("<- end   [baz]");
}

baz();

这是如何进行的.

  1. baz 被执行.在堆栈上分配的内存.
  2. foo 通过安排它运行next"来延迟.
  3. bar 通过安排它运行下一个"来延迟.
  4. baz 结束.堆栈被清除.
  5. 事件循环选择队列中的下一项 - 这是 foo.
  6. foo 被执行.在堆栈上分配的内存.
  7. foo 结束.堆栈被清除.
  8. 事件循环选择队列中的下一项 - 这是 bar.
  9. bar 被执行.在堆栈上分配的内存.
  10. bar 结束.堆栈被清除.
  1. baz is executed. Memory allocated on the stack.
  2. foo is delayed by scheduling it to run "next".
  3. bar is delayed by scheduling it to run "next".
  4. baz finishes. Stack is cleared.
  5. Event loop picks the next item on the queue - this is foo.
  6. foo gets executed. Memory allocated on the stack.
  7. foo finishes. Stack is cleared.
  8. Event loop picks the next item on the queue - this is bar.
  9. bar gets executed. Memory allocated on the stack.
  10. bar finishes. Stack is cleared.

如您所见,堆栈仍在进行中.您调用的任何函数都将始终生成一个堆栈条目.事件队列是一个单独的机制.

As you can hopefully see, the stack is still in play. Any function you call will always generate a stack entry. The event queue is a separate mechanism.

采用这种处理方式,您可以获得更少的内存开销,因为您不必等待任何其他函数来释放分配的资源.另一方面,您不能依赖任何功能的完成.

With this way of doing things, you get less memory overhead, since you do not have to wait on any other function to release the allocated resources. On the other hand, you cannot rely on any function being complete.

我希望这部分也能回答你的 Q3.

I hope this section also answers your Q3.

推迟到队列有什么帮助?

How does deferring to the queue help?

我希望上面的解释能让它更清楚,但要确保解释有意义:

I hope the above explanation will make it more clear but it bears making sure the explanation makes sense:

堆栈的深度有一定的限制.如果您考虑一下,应该很明显 - 只有这么多内存可以用于临时存储.一旦达到最大调用深度,JavaScript 将抛出 RangeError: Maximum call stack size exceeded 错误.

There is a set limit of how deep the stack could be. If you think about it, it should be obvious - there is only so much memory to spare for presumably temporary storage. Once the maximum call depth is reached, JavaScript will throw RangeError: Maximum call stack size exceeded error.

如果您查看我上面给出的 recursiveCountDown 示例,很容易导致错误 - 如果您调用 recursiveCountDown(100000),您将获得 <代码>RangeError.

If you look at the recursiveCountDown example I gave above that can very easily be made to cause an error - if you call recursiveCountDown(100000) you will get the RangeError.

通过将所有其他执行都放在队列中,您可以避免填满堆栈,从而避免 RangeError.所以让我们重新编写函数

By putting every other execution on the queue, you avoid filling up the stack and thus you avoid the RangeError. So let's re-write the function

// still naive but a bit improved recursive count down function
function betterRecursiveCountDown(count) {
  console.log("-> start recursiveCountDown [" + count + "]");
  
  if (count !== 0) {
    //setTimeout takes more than two parameters - anything after the second one will be passed to the function when it gets executed
    setTimeout(betterRecursiveCountDown, 0, count - 1);
    console.log("<- end recursiveCountDown [" + count + "]");
  } else {
    console.log("<<<- it's the final recursiveCountDown! [" + count + "]"); // show where we stopped
  }
}

betterRecursiveCountDown(10);

这篇关于了解 javascript 中的事件队列和调用堆栈的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆