为什么JS要等到功能完成后才改变背景颜色?



为什么我复制的时候背景没有改变?我添加了一个console.log(),正如你所看到的,console.log()工作,但背景不会改变。这里的问题是什么?

要测试,单击代码片段,然后按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函数调用:

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>

但是即使它在微任务中被调用,也不会改变什么,因为微任务也会阻塞事件循环。(阅读链接的答案,它解释了如何将呈现绑定到事件循环)。

如果你想让你的代码让浏览器做它的重绘,你需要让浏览器实际上循环事件循环,唯一的方法是:

  • 拆分您的getText逻辑,并通过发布任务(例如通过setTimeout)使其等待下一个事件循环迭代
  • 使用一个专用的Worker来生成getText返回的数据。

但是要注意,您没有使用异步剪贴板API,而是简单地覆盖复制事件的默认值,这不能异步完成。这样的话,你需要用到剪贴板的API

下面是一个使用MessageChannel来发布任务的例子,因为当前稳定的Chrome仍然有1ms的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>

首先,"then"应该是一个函数

.then(()=>{})

从运行你的代码,它看起来像这样

document.getElementById("object").style.backgroundColor = 'gray'; 

在将背景色设置为白色后立即被调用,因此您无法注意到它变为白色,即使它(只是非常非常短暂地)。

尝试在tryCopyAsync函数中设置一些日志记录,看看为什么它完成得太快了。

最新更新