单线程JavaScript下的动画
JavaScript是一种单线程语言,因此它一次只执行一个命令。异步编程是通过浏览器管理的Web API(DOM用于事件处理,XMLHttpRequest用于AJAX调用,WindowTimer用于setTimeout)和事件队列来实现的。到目前为止,一切都很好!现在考虑下面的非常简单的代码:单线程JavaScript下的动画,javascript,jquery-animate,single-threaded,Javascript,Jquery Animate,Single Threaded,JavaScript是一种单线程语言,因此它一次只执行一个命令。异步编程是通过浏览器管理的Web API(DOM用于事件处理,XMLHttpRequest用于AJAX调用,WindowTimer用于setTimeout)和事件队列来实现的。到目前为止,一切都很好!现在考虑下面的非常简单的代码: $('#mybox').hide(17000); console.log('Previous command has not yet terminated!'); ... 有人能给我解释一下上述的潜在
$('#mybox').hide(17000);
console.log('Previous command has not yet terminated!');
...
有人能给我解释一下上述的潜在机制吗?由于.hide()尚未完成(动画持续17秒),JS引擎正在处理它,并且它能够一次执行一个命令,因此它以何种方式转到下一行并继续运行剩余的代码
如果您的答案是动画创建承诺,那么问题仍然是相同的:JavaScript如何同时处理多个事情(执行动画本身,在承诺的情况下观察动画队列并继续执行下面的代码…)
此外,我无法解释jQuery中的承诺是如何工作的,如果它们必须监视它们的父延迟对象,直到它被解析或拒绝,这意味着代码执行,同时执行剩余的代码。在单线程方法中如何实现这一点?我理解AJAX调用没有问题,因为我知道它们是从JS引擎中删除的…javascript中有几种函数: 阻塞和非阻塞 非阻塞函数将立即返回,事件循环在后台等待调用回调函数时继续执行(如Ajax承诺) 动画依赖于setInterval和/或setTimeout,这两种方法立即返回,允许代码继续。回调被推回事件循环堆栈,执行,主循环继续 希望这会有帮助 您可以获得更多信息或 事件循环 JavaScript使用的是所谓的。事件循环类似于
while(true)
循环
为了简化它,假设JavaScript有一个巨大的数组,它在其中存储所有事件。事件循环在此事件循环中循环,从最早的事件开始循环到最新的事件。也就是说,JavaScript执行如下操作:
while (true) {
var event = eventsArray.unshift();
if (event) {
event.process();
}
}
如果在事件处理过程中(event.process
),触发了一个新事件(我们称之为eventA
),则新事件将保存在eventsArray
中,并继续执行当前事件。当前事件处理完成后,将处理下一个事件,依此类推,直到到达eventA
来到您的示例代码
$('#mybox').hide(17000);
console.log('Previous command has not yet terminated!');
执行第一行时,将创建事件侦听器并启动计时器。假设jQuery使用100ms帧。创建了一个100ms的计时器,带有回调函数。计时器开始在后台运行(该实现在浏览器内部),同时控件返回给脚本。因此,当计时器在后台运行时,脚本继续执行第二行。100毫秒后,计时器结束,并触发一个事件。此事件保存在上面的eventsArray
中,不会立即执行。代码执行完毕后,JavaScript会检查eventsArray
,发现有一个新事件,然后执行它
然后运行事件,您的div或任何元素移动几个像素,新的100ms计时器启动
请注意,这是一个简化,而不是整个事情的实际工作。整件事有一些复杂的地方,比如堆栈和所有的东西。有关更多信息,请参阅MDN文章。tl;博士如果没有外部帮助,在严格的单线程环境中是不可能实现的
我想我理解你的问题。让我们把一些事情弄清楚: JavaScript总是同步的 语言规范中未定义异步API。所有函数,如
Array.prototype.map
或String.fromCharCode
始终同步运行*
代码将始终运行到完成。代码在被返回
、隐式返回
(到达代码末尾)或抛出
(突然)终止之前不会停止运行
JavaScript存在于一个平台中
JavaScript语言定义了一个称为:
通过这种方式,可以说现有系统提供了对象和设施的主机环境,从而完善了脚本语言的功能
在浏览器中运行JavaScript的主机环境称为或文档对象模型。它指定浏览器窗口如何与JavaScript语言交互。例如,在NodeJS中,主机环境是完全不同的
虽然所有JavaScript对象和函数都同步运行直至完成,但主机环境可能会公开自己的函数,这些函数不一定在JavaScript中定义。它们没有与标准JavaScript代码相同的限制,并且可能定义不同的行为-例如文档的结果。getElementsByClassName
是一个活动DOM,它的行为与普通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++编写的,并使用线程和操作系统工具同时并行运行。这种并发性可以是后台工作(比如调度计时器)或实际的并行性(同样是DOM的一部分,而不是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.
setTimeout(function() {
console.log("World");
});
console.log("Hello");
$('#mybox').hide(17000);
console.log('Previous command has not yet terminated!');
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);
}
function (speed, easing, callback) {
return speed == null || typeof speed === "boolean"
? cssFn.apply(this, arguments)
: this.animate(genFx(name, true), speed, easing, callback);
}
function animate(prop, speed, easing, callback) {
var empty = jQuery.isEmptyObject(prop),
optall = jQuery.speed(speed, easing, callback),
doAnimation = function () {
// Operate on a copy of prop so per-property easing won't be lost
var anim = Animation(this, jQuery.extend({},
prop), optall);
// Empty animations, or finishing resolves immediately
if (empty || jQuery._data(this, "finish")) {
anim.stop(true);
}
};
doAnimation.finish = doAnimation;
return empty || optall.queue === false ? this.each(doAnimation) : this.queue(optall.queue, doAnimation);
}
function Animation(elem, properties, options) {
var result, stopped, index = 0,
length = animationPrefilters.length,
deferred = jQuery.Deferred().always(function () {
// don't match elem in the :animated selector
delete tick.elem;
}),
tick = function () {
if (stopped) {
return false;
}
var currentTime = fxNow || createFxNow(),
remaining = Math.max(0, animation.startTime + animation.duration - currentTime),
// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
temp = remaining / animation.duration || 0,
percent = 1 - temp,
index = 0,
length = animation.tweens.length;
for (; index < length; index++) {
animation.tweens[index].run(percent);
}
deferred.notifyWith(elem, [animation, percent, remaining]);
if (percent < 1 && length) {
return remaining;
} else {
deferred.resolveWith(elem, [animation]);
return false;
}
},
animation = deferred.promise({
elem: elem,
props: jQuery.extend({},
properties),
opts: jQuery.extend(true, {
specialEasing: {}
},
options),
originalProperties: properties,
originalOptions: options,
startTime: fxNow || createFxNow(),
duration: options.duration,
tweens: [],
createTween: function (prop, end) {
var tween = jQuery.Tween(elem, animation.opts, prop, end, animation.opts.specialEasing[prop] || animation.opts.easing);
animation.tweens.push(tween);
return tween;
},
stop: function (gotoEnd) {
var index = 0,
// if we are going to the end, we want to run all the tweens
// otherwise we skip this part
length = gotoEnd ? animation.tweens.length : 0;
if (stopped) {
return this;
}
stopped = true;
for (; index < length; index++) {
animation.tweens[index].run(1);
}
// resolve when we played the last frame
// otherwise, reject
if (gotoEnd) {
deferred.resolveWith(elem, [animation, gotoEnd]);
} else {
deferred.rejectWith(elem, [animation, gotoEnd]);
}
return this;
}
}),
props = animation.props;
propFilter(props, animation.opts.specialEasing);
for (; index < length; index++) {
result = animationPrefilters[index].call(animation, elem, props, animation.opts);
if (result) {
return result;
}
}
jQuery.map(props, createTween, animation);
if (jQuery.isFunction(animation.opts.start)) {
animation.opts.start.call(elem, animation);
}
jQuery.fx.timer(
jQuery.extend(tick, {
elem: elem,
anim: animation,
queue: animation.opts.queue
}));
// attach callbacks from options
return animation.progress(animation.opts.progress).done(animation.opts.done, animation.opts.complete).fail(animation.opts.fail).always(animation.opts.always);
}