有人可以向我解释上述的基本机制吗?由于 .hide() 尚未完成(动画持续 17 秒)并且 JS 引擎正在处理它并且它能够一次执行一个命令,它以哪种方式转到下一行并继续运行剩余代码?
jQuery.fn.hide()
内部调用jQuery.fn.animate
which 调用jQuery.Animation
返回 jQuerydeferred.promise()
对象;也可以看看jQuery.Deferred()
该deferred.promise()
方法允许异步函数防止其他代码干扰其内部请求的进度或状态。
Promise
参见Promises/A+ , promises-unwrapping , Basic Javascript promise implementation attempt的描述;还有,什么是 Node.js?
jQuery.fn.hide
:
function (speed, easing, callback) {
return speed == null || typeof speed === "boolean"
? cssFn.apply(this, arguments)
: this.animate(genFx(name, true), speed, easing, callback);
}
jQuery.fn.animate
:
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);
}
jQuery.Animation
:
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);
}
当.hide()
被调用时,jQuery.Deferred()
会创建处理动画任务的 a。
这就是console.log()
所谓的原因。
如果在下一行调用之前开始的can review的includestart
选项,但不会阻止用户界面执行异步任务。.hide()
.hide()
console.log()
$("#mybox").hide({
duration:17000,
start:function() {
console.log("start function of .hide()");
}
});
console.log("Previous command has not yet terminated!");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<div id="mybox">mybox</div>
本机Promise
实现
function init() {
function $(id) {
return document.getElementById(id.slice(1))
}
function hide(duration, start) {
element = this;
var height = parseInt(window.getComputedStyle(element)
.getPropertyValue("height"));
console.log("hide() start, height", height);
var promise = new Promise(function(resolve, reject) {
var fx = height / duration;
var start = null;
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
height = height - fx * 20.5;
element.style.height = height + "px";
console.log(height, progress);
if (progress < duration || height > 0) {
window.requestAnimationFrame(step);
} else {
resolve(element);
}
}
window.requestAnimationFrame(step);
});
return promise.then(function(el) {
console.log("hide() end, height", height);
el.innerHTML = "animation complete";
return el
})
}
hide.call($("#mybox"), 17000);
console.log("Previous command has not yet terminated!");
}
window.addEventListener("load", init)
#mybox {
position: relative;
height:200px;
background: blue;
}
<div id="mybox"></div>