マクロタスク
- マクロタスクはタスクキルと呼ばれているもの
- イベントループで処理が回ってきたら一つずつ格納されているタスクを実行する
setTimeout
setInterval
requestAnimationFrame
// など
setTimeout
一定時間後に1度だけ特定の処理を行う
setInterval
一定時間ごとに特定の処理を繰り返す
requestAnimationFrame
画面のリフレッシュレート(1秒あたりの描画切り替え回数)に合わせて繰り返されるのでリフレッシュレートに沿って滑らかなアニメーションが実現することができる。
この記事のイラストが非常にわかりやすい
60Hzと144Hzのリフレッシュレートの画面があった場合、setIntervalでは固定で再生されるのに対して、requestAnimationFrameはリフレッシュレートに応じたアニメーションが再生される
requestAnimationFrameの利点としては要素の移動やインタラクションなどは CSS Transition や SVG アニメーションなどで十分な場面が多いが、それでアニメーションできないものもアニメーションさせられる。
具体的には HTML の属性値を連続的に変更したりバックグラウンドイメージの値を書き換えたり繰り返し処理ゆえアニメーションが可能になる。
// アニメーションさせる要素
const box = document.querySelector(".box");
// 移動量
let moveX = 0;
/**
* アニメーションの各フレームでの処理
* @param time
*/
const moveAnimation = () => {
// 移動量が400以下の場合に実行
if (moveX <= 400) {
moveX++;
// 移動量を要素に適用する
box.style.transform = `translateX(${moveX}px)`;
// ここで自分自身を呼び出して再帰処理をする
requestAnimationFrame(moveAnimation);
}
};
// アニメーションを実行する
moveAnimation();
document.querySelector("button").addEventListener("click",()=>{
if(moveX < 400){
return
}
moveX = 0;
moveAnimation()
})
マクロタスクを使うと
マクロタスクを使うことで非常に大きな処理を、分割して後で実行することができる
let i = 0;
let start = Date.now();
function count() {
// 重い処理の一部を実行 (*)
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert("Done in " + (Date.now() - start) + 'ms');
} else {
setTimeout(count); // 新たな呼び出しをスケジュール (**)
}
}
count();
そうすることで例えばイベントループが繰り返し回ることによって他のイベントを発火させるタイミングを与えることができる。例えばクリックイベントなどを挟み込む余地ができる。
あるいはタスクを分割することで進捗状況を把握する事もやりようによってはできるかもしれない。
マイクロタスク
- マイクロタスクはタスクキューとは別に存在する非同期処理待ちの行列(ジョブキュー)
- マイクロタスクとマクロタスクが同じイベントループで処理を行われた場合マイクロタスクが優先される
- イベントループで処理が回ってきたら全ての格納されているジョブを実行する
Promise
queueMicrotask
MutationObserver
// など
queueMicrotaskはPromise.resolve().thenのようなものと考えればいいのか?
おそらくマイクロタスクをスケジュールしたい時に利用する
console.log("start:main");
const testFn = v => () => {
console.log(v);
};
setTimeout(testFn("setTimeout"));
queueMicrotask(testFn("aaa"));
Promise.resolve().then(testFn("resolve"));
// 呼ばれてから登録するため少し時間遅い
queueMicrotask(() => queueMicrotask(testFn("aaa:r")));
Promise.resolve().then(() => Promise.resolve().then(testFn("resolve:r")));
// process.nextTick(() => process.nextTick(testFn("nt:r")));
Promise.resolve().then(testFn("resolve2"));
queueMicrotask(testFn("aaa2"));
// process.nextTick(testFn("nt2"));
console.log("end:main");
start:main
end:main
aaa
resolve
resolve2
aaa2
aaa:r
resolve:r
setTimeout
MutationObserverとは
その通り変化を監視する API。
2の要素の変化を監視することができる。
<!DOCTYPE html>
<ol contenteditable oninput="">
<li>Press enter</li>
</ol>
<script>
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var list = document.querySelector('ol');
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === 'childList') {
var list_values = [].slice.call(list.children)
.map(function (node) { return node.innerHTML; })
.filter(function (s) {
if (s === '<br>') {
return false;
}
else {
return true;
}
});
console.log(list_values);
}
});
});
observer.observe(list, {
attributes: true,
childList: true,
characterData: true
});
</script>
それぞれのオプションについて
attributes
対象ノードの属性に対する変更を監視する場合はtrue。
characterData
対象ノードのデータに対する変更を監視する場合はtrue。
subtree
対象ノードの子孫ノードまで監視する場合はtrue。
まとめ
-
新しいマクロタスクをスケジュールするには遅延0のセットタイムアウトを使用する。
- これはブラウザがユーザーイベントに反応したりタスクの進捗状況を表示することができるよう計算量の多いタスクを小さく分割するために使用される。
- またイベントが完全に処理された後にアクション行うようスケジュールを行うために使われることがある。
-
新しいマイクロタスクをスケジュールするにはqueueMicrotaskを使用する。