草庐IT

javascript - 单线程JavaScript下的动画

coder 2024-07-21 原文

JavaScript是一种单线程语言,因此它一次执行一个命令。正在通过浏览器管理的 Web API (用于事件处理的DOM,用于AJAX调用的XMLHttpRequest,用于setTimeout的WindowTimers)和事件队列来实现异步编程。到现在为止还挺好!现在考虑以下非常简单的代码:

$('#mybox').hide(17000);
console.log('Previous command has not yet terminated!');
... 

有人可以向我解释以上内容的潜在机制吗?由于.hide()尚未完成(动画持续17秒),并且JS引擎正在处理它,并且它能够一次执行一个命令,因此它将转到下一行并继续运行剩下的代码?

如果您的回答是动画创建了 promise ,的问题仍然相同:JavaScript如何同时处理多个事情(执行动画本身,在出现 promise 的情况下观看动画队列并继续执行以下代码跟随...)。

而且,我无法解释如果 promise 必须在解决或拒绝父deferred对象(直到代码被解析或拒绝)之前jQuery的 promise 如何工作,这意味着代码执行,同时执行其余代码。单线程方法怎么可能?我了解AJAX调用没有问题,因为我知道它们已从JS引擎中删除了...

最佳答案

tl; dr; ,在没有外部帮助的情况下,在严格的单线程环境中是不可能的。

我想我了解您的问题。让我们解决一些问题:

JavaScript始终是同步的

语言规范中未定义异步API。像Array.prototype.mapString.fromCharCode这样的所有功能始终同步运行*。

代码将始终运行到完成。在被return,隐式return(到达代码的末尾)或throw(突然)终止之前,代码不会停止运行。

a();
b();
c();
d(); // the order of these functions executed is always a, b, c, d and nothing else will 
     // happen until all of them finish executing

JavaScript驻留在平台内

JavaScript语言定义了一个称为host environment的概念:

In this way, the existing system is said to provide a host environment of objects and facilities, which completes the capabilities of the scripting language.



在浏览器中运行JavaScript的主机环境称为DOM或文档对象模型。它指定浏览器窗口如何与JavaScript语言进行交互。例如,在NodeJS中,主机环境是完全不同的。

尽管所有JavaScript对象和函数都同步运行以完成操作,但宿主环境可能会公开自己的功能,而这些功能不一定是在JavaScript中定义的。它们没有标准JavaScript代码所具有的相同限制,并且可能定义了不同的行为-例如document.getElementsByClassName的结果是实时DOM NodeList ,它的行为与普通JavaScript代码有很大不同:
var els = document.getElementsByClassName("foo"); 
var n = document.createElement("div");
n.className = "foo";
document.body.appendChild(n);
els.length; // this increased in 1, it keeps track of the elements on the page
            // it behaves differently from a JavaScript array for example. 

这些主机功能中的某些功能必须执行I/O操作(如计划计时器),执行网络请求或执行文件访问。像所有其他API一样,这些API必须运行完整。这些API由宿主平台提供-它们调用您的代码不具备的功能-通常(但不一定)它们是用C++编写的,并使用线程和操作系统工具来并行和并行运行事物。这种并发可以仅仅是后台工作(如安排计时器)或实际的并行性(如WebWorkers-也是DOM的一部分,而不是JavaScript的一部分)。

因此,当您在DOM上调用诸如setTimeout之类的 Action ,或应用导致CSS动画的类时,它并不会受到代码所具有的相同要求的约束。它可以使用线程或操作系统async io

当您执行以下操作时:
setTimeout(function() {
   console.log("World");
});
console.log("Hello");

实际发生的是:
  • 主机函数setTimeout通过类型为function的参数进行调用。它将函数插入queue in the host environment
  • console.log("Hello")是同步执行的。
  • 所有其他同步代码都运行(注意,setTimeout调用在此是完全同步的)。
  • JavaScript已完成运行-控制权已转移到主机环境。
  • 主机环境注意到它在计时器队列中有东西,并且已经经过了足够的时间,因此它调用其参数(函数)-console.log("World")被执行。
  • 函数中的所有其他代码均同步运行。
  • 将控制权交还给主机环境(平台)。
  • 主机环境中还发生了其他事情(鼠标单击,AJAX请求返回,计时器触发)。主机环境调用用户传递给这些操作的处理程序。
  • 再次所有JavaScript均同步运行。
  • 等等,依此类推...

  • 您的具体情况
    $('#mybox').hide(17000);
    console.log('Previous command has not yet terminated!');
    

    这里的代码是同步运行的。前面的命令已终止,但是实际上并没有做太多-而是在平台a(在.hide(17000)中)安排了回调,然后再次执行console.log-所有JavaScirpt代码始终始终同步运行。

    那就是-hide只执行很少的工作,并运行几毫秒,然后安排更多的工作在以后进行。它不会运行17秒钟。

    现在,hide的实现如下所示:
    function hide(element, howLong) {
        var o = 16 / howLong; // calculate how much opacity to reduce each time
        //  ask the host environment to call us every 16ms
        var t = setInterval(function
            // make the element a little more transparent
            element.style.opacity = (parseInt(element.style.opacity) || 1) - o;
            if(parseInt(element.style.opacity) < o) { // last step
               clearInterval(t); // ask the platform to stop calling us
               o.style.display = "none"; // mark the element as hidden
            }
        ,16);
    }
    

    因此,基本上我们的代码是单线程的-它要求平台每秒调用60次,并使该元素每次不那么可见。一切总是可以完成的,但除了第一个代码执行,平台代码(主机环境)正在调用我们的代码,反之亦然。

    因此,对于您的问题的实际直接答案是,与您发出AJAX请求时类似,计算时间从代码中“夺走了”。要直接回答:

    没有外部帮助,在单线程环境中是不可能的。

    外部是使用线程或操作系统异步功能的封闭系统-我们的主机环境。没有纯标准的ECMAScript,就无法做到这一点。

    *在ES2015中包含了promise,该语言将任务委派回平台(主机环境)-但这是一个异常(exception)。

    关于javascript - 单线程JavaScript下的动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35362458/

    有关javascript - 单线程JavaScript下的动画的更多相关文章

    1. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

      我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

    2. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

      Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

    3. ruby - 如何让Ruby捕获线程中的语法错误 - 2

      我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

    4. ruby - 如何在 ruby​​ 中运行后台线程? - 2

      我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

    5. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

      我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

    6. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

      我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

    7. ruby - Ruby 1.9.1 中的 native 线程,对我有什么好处? - 2

      所以,Ruby1.9.1现在是declaredstable.Rails应该与它一起工作,并且正在慢慢地将gem移植到它。它具有native线程和全局解释器锁(GIL)。自从GIL到位后,原生线程是否比1.9.1中的绿色线程有任何优势? 最佳答案 1.9中的线程是原生的,但它们被“放慢了速度”,一次只允许一个线程运行。这是因为如果线程真的并行运行,它会混淆现有代码。优点:IO现在在线程中是异步的。如果一个线程阻塞在IO上,那么另一个线程将继续执行直到IO完成。C扩展可以使用真正的线程。缺点:任何非线程安全的C扩展都可能存在使用Thre

    8. ruby - 使写入文件线程安全 - 2

      我在一个ruby​​文件中有一个函数可以像这样写入一个文件File.open("myfile",'a'){|f|f.puts("#{sometext}")}这个函数在不同的线程中被调用,使得像上面这样的文件写入不是线程安全的。有谁知道如何以最简单的方式使这个文件写入线程安全?更多信息:如果重要的话,我正在使用rspec框架。 最佳答案 您可以通过File#flock给锁File.open("myfile",'a'){|f|f.flock(File::LOCK_EX)f.puts("#{sometext}")}

    9. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

      我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

    10. LVGL V8动画 - 2

      动画/*INITIALIZEANANIMATION 初始化一个动画*-----------------------*/lv_anim_ta;lv_anim_init(&a);/*MANDATORYSETTINGS 必选设置*------------------*//*Setthe"animator"function 设置“动画”功能*/lv_anim_set_exec_cb(&a,(lv_anim_exec_xcb_t)lv_obj_set_x);/*Setthe"animator"function*/lv_anim_set_var(&a,obj);/*Lengthoftheanim

    随机推荐