我想看看是否会异步触发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()
取决于浏览器/实现?
答案 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周期。