当前位置: 首页 > 知识库问答 >
问题:

javascript - 状态锁似乎失效了,请问是为什么?

陈浩
2024-09-23
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <input type="checkbox" id="bigger_view" name="bigger_view" value="bigger_view" /><label for="bigger_view">切换</label>
</body>
<script>
    var bigger_view = document.querySelector("#bigger_view")
    var is_runing = false
    
    bigger_view.onchange = ()=>{
        if(is_runing){
            return
        }
        
        is_runing = true
        bigger_view.disabled = true
        
        run_task(()=>{
            is_runing = false
            bigger_view.disabled = false
        })
    }
    
    function run_task(callback){
        console.log("is_runing")
        for(var i=0; i<999999999*4; i++){
            
        }
        
        callback("finish")
    }
</script>
</html>

以上代码,点击 checkbox 后(此时 run_task 还未完成)快速再次点击,run_task 会执行两次,请问大家这是为什么呢?

共有2个答案

邹宣
2024-09-23

你可以添加一个点击事件:

bigger_view.onclick = () => {
  console.log(bigger_view.disabled);
};

可以看到,每次点击的时候 disabled 都是 false,这是由于「事件循环」,就像 AI 说的那样。

简单来说,浏览器运行 JS 时,会保存一个任务队列,主线程空闲时会不断取出队列中的任务并运行。更详细、更准确的描述请参考网上的资料。

将长 for 循环改为更加准确的 while 循环并控制它阻塞 5 秒:

  const start = Date.now();
  while (Date.now() - start < 1 * 5000);

点击 checkbox,等待 2 秒再次点击,将产生如下结果:

false
is_running
(5 秒后)
finish
false
is_running
(5 秒后)
finish

当你点击 checkbox 并触发 onchange 事件时,onchange 函数不会立即运行,而是将这个函数作为一个任务添加到任务队列中。由于第一个 onchange 事件还没有运行完成,所以第二个 onchange 函数要等第一个运行完才能执行。而第一个函数运行完成时,已经将 disabled 设置为 false 了。

麹高远
2024-09-23

这个问题是由于JavaScript在浏览器中运行时的单线程模型以及事件循环的工作方式导致的。在你的代码中,run_task 函数通过一个非常大的循环来模拟一个耗时的任务,尽管这个循环实际上并不执行任何计算密集型操作(即它只是空转),但它仍然会阻塞执行线程,使得UI更新(比如设置checkbox的disabled属性)和事件处理被延迟。

这里的关键点是,JavaScript运行在浏览器中时,它的执行环境是单线程的。这意味着在任何给定时间点,只有一个JavaScript代码块可以在执行栈上运行。浏览器还会维护一个任务队列(也称为事件队列),其中包含等待执行的事件处理程序和其他需要由JavaScript环境处理的任务。

当你第一次点击checkbox时,onchange事件处理程序开始执行。它首先检查is_runing标志(此时为false),然后将其设置为true,禁用checkbox,并调用run_task函数。由于run_task内部有一个长时间运行的循环,这个循环会阻塞线程,导致回调函数(callback)只有在循环完成后才会被调用。

然而,由于JavaScript的执行被阻塞,浏览器的UI更新(即checkbox的禁用状态)和事件队列的处理都被推迟了。如果在run_task的循环完成之前(即在callback执行之前),用户再次点击了checkbox,此时is_runing仍然为true(因为回调函数还未执行),但由于浏览器的事件循环机制,第二次点击的事件处理程序仍然会被加入到事件队列中等待执行。

run_task的循环完成后,它会调用回调函数,将is_runing设置为false并重新启用checkbox。但此时,第二个点击事件的处理程序已经在事件队列中等待,并且当浏览器回到执行JavaScript代码时,它会继续执行这个事件处理程序。由于此时is_runing已经再次被设置为false,第二次点击的处理程序将再次启动run_task,导致你观察到的行为。

要解决这个问题,你可以考虑使用更高级的异步处理方法,如Promiseasync/awaitsetTimeout等,来避免长时间阻塞UI线程。然而,对于简单的模拟或演示,你也可以通过限制点击操作的频率(例如使用防抖或节流技术)来避免这种重复执行的问题。

 类似资料:
  • 当我的应用程序长时间运行时,一切都会正常运行。但当我将列类型从int更改为text(删除表并重新创建)时,我发现了一个异常: 这个异常偶尔会出现。我正在使用准备语句执行查询,我认为它是从DataStax的驱动程序缓存的。 我正在使用AWS Keyspace(Cassandra版本3.11.2),DataStax驱动程序4.6。这是我的application.conf:

  • 之前没问题但有一个弹框样式不能用scoped,所以删了一下,然后不管怎么撤回样式不生效了! 代码:

  • 问题内容: 我的活动课在这里: 和相机预览类在这里: 但是,当我测试该类时,似乎首先调用onResume(),然后在1或2秒后再次调用。因此,相机必须再次刷新。如果我根本没有onResume(),则摄像头预览稳定,但是如果我从主屏幕或其他某个应用再次切换到该应用,则会崩溃。我发现onPause()不会影响任何一个。我的代码正确吗?我应该添加/删除哪些内容以使其不会再次刷新并且在应用切换后仍然不会崩

  • 本文向大家介绍请问什么是死锁(deadlock)?相关面试题,主要包含被问及请问什么是死锁(deadlock)?时的应答技巧和注意事项,需要的朋友参考一下 考察点:线程死锁 两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。 例如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生

  • 接口状态是Abort,HTTP Status为0,这可能是什么问题?之前好好的,今天去看成这样了。 接口没有返回信息。

  • 问题内容: 因此,我有此标记,并且在其内部设置了图像顶部的蓝色叠加层。 如果我不制作 ,标题文本将隐藏在蓝色层的后面…好像它的用法在模仿 为什么会这样呢? 问题答案: 您需要参考规范,更确切地说是绘画顺序,以了解何时打印每一层。 在没有元素的情况下,未放置元素并将在步骤(4)中进行打印: 对于其所有 流入的,未定位的, 块级的树状后代:如果元素是块,列表项或其他等效块: 然后在步骤(8)中打印定位