setTimeout()是否在单独的线程上运行?

时间:2018-08-22 06:22:50

标签: javascript settimeout

我想看看是否会异步触发setTimeout(),所以尝试了以下测试脚本:

function timedText() {
  var x = document.getElementById("txt");
  setTimeout(function() {
    x.value = "1 second"
  }, 1000);
  setTimeout(function() {
    x.value = "2 seconds"
  }, 2000);
  setTimeout(function() {
    x.value = "3 seconds"
  }, 3000);

  while (true) {}
}
<p>Click on the button below. The input field will tell you when two, four, and six seconds have passed.</p>

<button onclick="timedText()">Display timed text</button>
<input type="text" id="txt">

果然,单击按钮会使浏览器挂起

这告诉我setTimeout()不会在单独的线程上运行。

但是在最近的一次采访中,访调员提出了其他建议……这是否意味着setTimeout()取决于浏览器/实现?

5 个答案:

答案 0 :(得分:7)

JavaScript不是多线程的。好吧,有WebWorkers运行在不同的线程中,但这并不是真正的多线程,更像是多个进程,它们彼此通信。

while (true) {}会阻塞js上下文,因为这是一个无休止的循环。

setTimeout将注册一个函数供以后执行。但是在同一上下文中,任何时候代码都不会并行运行。

while (true)本身不一定会创建阻塞循环:

async function sleep(time) {
  return new Promise((resolve, _) => setTimeout(resolve, time))
}

async function test(val) {
  while (true) {
    console.log('in while loop ' + val)
    await sleep(1000)
  }
}

test('foo')
test('bar')

因此,您可以说,await / async可以创建某种协作式多任务处理,例如设置,但仍然没有多线程处理

答案 1 :(得分:3)

javascript中没有线程。 setTimeout仅将委托函数推入堆栈,该堆栈将在下一次传递时弹出。 您可以阅读JavaScript and Threads

答案 2 :(得分:1)

那些异步功能由浏览器处理。不是JavaScript引擎。 JavaScript中没有线程。

答案 3 :(得分:1)

  

这告诉我setTimeout()不在单独的线程上运行。

是的。 JS中只有一个线程。

  

但是在最近的一次采访中,面试官提出了其他建议...   那意味着setTimeout()与浏览器/实现有关吗?

据我所知,只有引擎在浏览器之间发生了变化。内部机制是一样的-事件循环处理器。

答案 4 :(得分:1)

调用setTimeout()时,通常控制权会传递回主机环境(例如浏览器或本机node.js代码)。然后发生的事情是您的回调已在计时器列表中注册,以备将来执行。 setTimeout()将返回您的代码,该代码将继续执行。

当脚本最终完成时,控制权将再次返回到具有事件循环的主机环境,该循环不断旋转,直到最后一次调用已注册的回调为止。

实际上,您可以通过实现一个有趣的事件循环来在JavaScript本身中近似这样的内容:

class EventLoop {

    constructor() {
        this.entries = [];  // a list of all registered callbacks
        this.turns = 0;     // keep track of how many turns of the loop we make
    }

    // Adds a new callback to the list

    schedule(callback, condition) {
        this.entries.push([condition, callback]);
    }

    // To removes a callback when it's been called

    remove(entry) {
        this.entries.splice(this.entries.indexOf(entry), 1);
    }

    // Run the loop until all registered callbacks were called
    // Returns the number of turns it made in the while loop

    run(timeout) {

        this.turns = 0;

        while (this.entries.length) {
            for (const entry of this.entries) {
                const [condition, callback] = entry;
                if (condition()) {
                    callback();
                    this.remove(entry);
                }
            }

            this.turns++;
        }

        return this.turns;
    }

}

我们可以使用此EventLoop来实现类似setTimeout()的东西:

// Define a handy log function

const log = ((startTime) => (text) => {

    console.log(`t+${(Date.now() - startTime).toFixed(3)}ms: ${text}`);

})(Date.now());

// Create an event loop

const loop = new EventLoop();

// Define a setTimeout using the event loop

const defer = (fn, timeout) => {

    const start = Date.now();
    const end = start + timeout;

    loop.schedule(fn, () => Date.now() >= end);
};

// Schedule some nested events

defer(() => {

    log('I run second');

    defer(() => {

        log('I run third');

        defer(() => {

            log('I run fourth');

        }, 200);

    }, 200);

}, 200);

// Log syncronously

log('I run first');

// Start the event loop turning (blocks until all events are complete)

const turns = loop.run();

log(`Loop exited after ${turns} turns`);

// This will log after event loop has finished running

log('I run last');

如果使用node.js运行此命令,则会得到以下输出:

t+0.000ms: I run first t+200.000ms: I run second t+400.000ms: I run third t+600.000ms: I run fourth t+600.000ms: Loop exited after 6441157 turns t+600.000ms: I run last

我们刚刚用一个线程在纯JavaScript中创建了一个异步超时。现在实际上您不会在JavaScript中执行此操作,事件循环将以本机代码实现并托管在宿主环境中。此类事件循环的一个示例是Node.js使用的libuv。 Libuv可以比我们的玩具JS示例更有效地执行操作,它可以使当前线程进入睡眠状态(从技术上讲,它不执行此操作,它轮询IO,但使用相同的概念),因此不会浪费CPU周期。