为什么JS为什么要等到功能完成才可以更改背景色? [英] Why does JS wait until function is complete before changing background color?
问题描述
为什么在我复印时背景没有正确改变?我添加了 console.log()
,并且您可以看到, console.log()
可以工作,但是背景不会改变.这是什么问题?
Why doesn't the background change right as I copy? I added a console.log()
and as you can see, the console.log()
works, but the background won't change. What is the problem here?
要进行测试,请点击代码段,然后按 CMD + C
(Windows: CTRL + C
)
To test, click on the snippet and then press CMD + C
(Windows:CTRL + C
)
window.addEventListener('copy', function(e) {
e.preventDefault();
//This should change!
document.getElementById("object").style.backgroundColor = 'white';
console.log("Started!");
tryCopyAsync(e).then(() =>
document.getElementById("object").style.backgroundColor = 'gray'
);
});
async function tryCopyAsync(e){
if(navigator.clipboard){
await e.clipboardData.setData('text/plain',getText());
}
}
function getText(){
var html = '';
var row = '<div></div>';
for (i=0; i<100000; i++) {
html += row;
}
return html;
}
#object{
width:100%;
height:100vh;
background:gray;
}
body{
padding:0;
margin:0;
overflow:hidden;
}
<div id='object'></div>
推荐答案
首先,即使调用它,您的 sleepFor
方法也将完全阻塞事件循环 来自 async
函数:
First, your sleepFor
method is completely blocking the event-loop synchronously, even if it is called from an async
function:
window.addEventListener('copy', function(e) {
e.preventDefault();
//This should change!
document.getElementById("object").style.backgroundColor = 'white';
console.log("Started!");
tryCopyAsync(e).then(() =>
document.getElementById("object").style.backgroundColor = 'gray'
);
console.log('sync');
});
async function tryCopyAsync(e){
if(navigator.clipboard){
await e.clipboardData.setData('text/plain',getText());
}
}
function sleepFor(sleepDuration){
var now = new Date().getTime();
while(new Date().getTime() < now + sleepDuration){}
}
function getText(){
console.log('blocking');
var html = '';
var row = '<div></div>';
for (i=0; i<10; i++) {
html += row;
sleepFor(300);
}
console.log('stopped blocking');
return html;
}
onclick = e => document.execCommand('copy');
#object{
width:100%;
height:100vh;
background:gray;
}
body{
padding:0;
margin:0;
overflow:hidden;
}
click to trigger the function
<div id='object'></div>
But even if it were called in a microtask, that wouldn't change a thing, because microtasks also do block the event-loop. (Read that linked answer, it explains how the rendering is tied to the event-loop).
如果要让代码让浏览器进行重绘,则需要让浏览器实际循环事件循环,并且唯一的方法是:
If what you want is to have your code let the browser do its repaints, you need to let the browser actually loop the event-loop, and the only ways to do this are:
- 拆分您的
getText
逻辑,并通过发布任务(例如通过setTimeout
)使其等待下一次事件循环迭代 - 使用专用工作者来生成返回的数据通过
getText
.
- split your
getText
logic and make it wait for the next event-loop iteration by posting a task (e.g throughsetTimeout
) - use a dedicated Worker to produce the data returned by
getText
.
但是请注意,您不是在使用异步剪贴板API,而只是覆盖了复制事件的默认值,该值不能异步完成.因此,按照这种方式,您实际上将需要真正使用剪贴板API.
However beware you were not using the async Clipboard API, but simply overriding the default value of the copy event, which can not be done asynchronously. So going this way you will actually need to really use the Clipboard API.
以下是使用 MessageChannel 进行发布的示例任务,因为当前稳定的Chrome仍然对 setTimeout
Here is an example using a MessageChannel to post a task since current stable Chrome still has a 1ms minimum delay for setTimeout
:
window.addEventListener('copy', function(e) {
e.preventDefault();
//This should change!
document.getElementById("object").style.backgroundColor = 'white';
console.log("Started!");
tryCopyAsync(e).then(() =>
document.getElementById("object").style.backgroundColor = 'gray'
);
});
async function tryCopyAsync(e) {
if (navigator.clipboard) { // you were not using the Clipboard API here
navigator.clipboard.writeText(await getText());
}
}
async function getText() {
var html = '';
var row = '<div></div>';
for (i = 0; i < 1000000; i++) {
if (i % 1000 === 0) { // proceed by batches of 1000
await waitNextFrame();
}
html += row;
}
return html;
}
function waitNextFrame() {
return new Promise(postTask);
}
function postTask(task) {
const channel = postTask.channel ||= new MessageChannel();
channel.port1.addEventListener("message", () => task(), {
once: true
});
channel.port2.postMessage("");
channel.port1.start();
}
onclick = (evt) => document.execCommand("copy");
#object {
width: 100%;
height: 100vh;
background: gray;
}
body {
padding: 0;
margin: 0;
overflow: hidden;
}
<div id='object'></div>
这篇关于为什么JS为什么要等到功能完成才可以更改背景色?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!