Miscellaneous¶
Mutation observer¶
<https://javascript.info/mutation-observer> のノート。
MutationObserver を学ぶ。DOM ノードを監視し、変化を検出したときにコールバックを呼び出すものだ。典型的な Observer パターンだ。
Syntax¶
let observer = new MutationObserver(callback);
observer.observe(node, config);
ここで config はどの種類の変化に反応するかを指定する key-value オブジェクトで、値は真偽型だ。
childListnodeの子の変化subtreenodeの子孫すべての変化attributesnodeの属性の変化characterDatanode.dataを観察するかどうかattributeOldValuecallbackに新旧両方の属性値を渡すかどうか(否ならば変化後の値のみ渡す)。characterDataOldValuecallbackに新旧両方のnode.dataの値を渡すかどうか
config.attributeFilter は属性の名前の配列。そこにあるものしか観察しない。
コールバック関数は次のような形だ。
function callback(mutations, observer){ /* ... */ }
引数
mutationsは次で述べる key-value 仕様のオブジェクトの配列type: 変化の種類を表す値。文字列"attributes","characterData","childList"のいずれか。target: 変化したノード。addedNodes: 追加されたノード。removedNodes: 削除されたノード。previousSibling: 追加削除変化のあったノードの兄ノード。nextSibling: 追加削除変化のあったノードの弟ノード。その他
引数
observerは観察しているMutationObserverオブジェクト自身
Usage for integration¶
広告スペースなどの不要な要素が DOM に現れたときにそれを検出し、削除することができる。
DOM に何らかのノードが動的に変化したとき、関連ノードをリサイズする。
その他
Usage for architecture¶
外部からロードするなど、動的に生成した HTML に対して、さらに動的に何か装飾的な処理をしたい場合に MutationObserver を応用するのは適している。
Dynamic highlight demo¶
MutationObserver のコールバックでは mutations をフィルターして、所望のノードをまずは得る。フィルターとして次のものを駆使する:
演算子
instanceofメソッド
node.matches()メソッド
node.querySelectorAll()
Additional methods¶
観察をやめるためのメソッドが用意されている。
observer.disconnect(): 観察をやめる。このとき未済の変化があり得る。observer.takeRecords():callbackが処理していないmutationsを返す。呼び出すと観察キューのような領域から
上記のメソッドを両方利用する場合、後者を先に呼ぶ。
Selection and Range¶
<https://javascript.info/selection-range> のノート。
ページ内の選択と、フォームフィールド内の選択に分類する。
JavaScript は既存の選択範囲にアクセスしたり、DOM ノードの全体または一部を選択・解除したり、選択内容をページから削除したり、タグに包んだりすることができる。
Range¶
選択範囲の基本は Range で、開始点と終了点で表現される。このオブジェクトは、引数なしで作成する。それから range.setStart(node, offset) と
range.setEnd(node, offset) を呼び出して選択範囲の境界を設定する。始点は対象範囲の最初の点であり、終点は対象範囲の上界の最初の点だ。
この二つのメソッドはペアで利用するが、第一引数の
nodeは共通の値である必要はない。
Selecting the text partially¶
第二引数 offset は、第一引数 node がテキストノードならば、テキスト文字列のインデックスを指定する。
Selecting element nodes¶
第二引数 offset は、第一引数 node が要素ならば、子のインデックスを指定する。
Selecting a bigger fragment¶
この二つのメソッドはペアで利用するが、指定範囲がテキスト型とノード型をまたがっても構わない。
Range properties¶
Range のプロパティーを見ていく。
Property |
Description |
|---|---|
|
開始のノードとインデックス |
|
終了のノードとインデックス |
|
範囲が空ならば |
|
範囲内にあるノードすべての先祖であって最も若いノード |
Range selection methods¶
setStart, setEnd で何でもできるが、範囲を操作するための便利なメソッドがたくさんある。いずれも node はテキストノード、要素のどちらでも構わない。
Method |
Description |
|---|---|
|
開始位置を |
|
開始位置を |
|
開始位置を |
|
終了位置を |
|
終了位置を |
|
終了位置を |
他にもこういうものがある:
Method |
Description |
|---|---|
|
|
|
|
|
始点に合わせるか終点に合わせるかを指定して範囲を空にする |
|
範囲オブジェクトを複製する |
Range editing methods¶
範囲を操作するメソッドがいくつか用意されている。
Method |
Description |
|---|---|
|
収容物をページから削除する |
|
収容物をページから取り除くと同時にそれを
|
|
収容物を複製してそれを
|
|
|
|
範囲を |
Selection¶
ページの選択範囲は Selection オブジェクトで表現される。
window.getSelection()やdocument.getSelection()で取得することができる。選択範囲には 0 個以上の
Rangeを含めることができる。
Selection properties¶
Selection オブジェクトから i 番目の Range オブジェクトを得るにはメソッド getRangeAt(i) を使う。
Selection の主なプロパティー:
Property |
Description |
|---|---|
|
選択がここで始めるというノード |
|
|
|
選択がここで終わるというノード |
|
|
|
選択が空ならば |
|
選択中にある範囲の個数 |
アンカーとフォーカスの前後関係は固定されていない。
Selection events¶
elem.onselectstart: 要素elem上で選択が開始されるときのイベント。document.onselectionchange: 選択が変更または開始されたときのイベント。文書内の選択すべてを追跡する。
Selection tracking demo¶
選択によってはアンカーもフォーカスも undefined となるようだ。
Selection copying demo¶
プレーンテキストとして複製するか、書式を保ちつつ複製するかで方法を変える。
document.getSelection().toString(): プレーンテキストとして内容を得る。Selectionを構成する各Rangeに対してcloneContents()を順次呼び出す。戻り値のDocumentFragmentを得られた順に何かする。
Selection methods¶
基本的なメソッド:
Method |
Description |
|---|---|
|
|
|
(選択に含まれていない?)範囲を追加する |
|
指定の範囲を削除する |
|
範囲すべてを削除する |
|
同上 |
便利メソッド:
collapse(node, offset)現在の選択範囲を
nodeのoffset位置を指す空範囲とするsetPosition(node, offset)同上
collapseToStart()選択を現在のそれの開始位置にある空範囲に縮める
collapseToEnd()上の終了位置版
extend(node, offset)選択の focus を
nodeのoffsetに動かすsetBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)いちばん詳細な指定で選択
selectAllChildren(node)nodeの子すべてを選択するdeleteFromDocument()内容をページから削除する
containsNode(node, allowPartialContainment = false)nodeを含んでいるか
置換でない方法のメソッドを使って選択を指定し直すときには、まず選択をリセットしてからメソッドを呼び出すのが安全だ。
Selection in form controls¶
INPUT や TEXTAREA のようなフォーム要素は、より単純な API で選択を取り扱う。
Property |
Description |
|---|---|
|
選択開始位置 |
|
選択終了位置 |
|
選択方向を表す文字列 “forward”, “backward”, “none” |
Event |
Description |
|---|---|
|
何かが選択されたときのイベント |
select()テキストボックスにあるすべてを選択する
setSelectionRange(start, end, [direction])テキストを選択する
setRangeText(replacement, [start], [end], [selectionMode])テキスト範囲を置換する
オプション引数 selectionMode は置換後の選択状態を指定する文字列だ:
"select"新しいテキストが選択となる。
"start"新しいテキストの先頭が選択(空)となる。
"end"新しいテキストの末尾が選択(空)となる。
"preserve"なるべく選択を保つ。
Example: tracking selection¶
イベント
onselectは選択を削除した瞬間には発動しない。テキストボックス内の選択イベントは
document.onselectionchangeを引き起こさい。
Example: moving cursor¶
selectionStart と selectionEnd を変更することで、選択範囲を設定することができる。両者を同じ値にすると、キャレットをその位置に移動させる。
Example: modifying selection¶
選択範囲の内容を変更するには、強力なメソッド setRangeText を使用する。
最も単純な引数一つの呼び出しでは、ユーザーが選択した範囲を置換してから範囲を解除する。
デモのように、すべての引数を指定すると置換後のテキストが選択される。
Example: insert at cursor¶
空範囲に対してメソッド setRangeText を使用すると、そこにテキストを追加することになる。
Making unselectable¶
何かを選択不能にする三つの方法が述べられている。
CSS で
user-select: noneを与える。onselectstartまたはonmousedownイベントで既定の処理を無効化する。選択が起きた後に
document.getSelection().empty()を呼び出す。
最後の方法は画面がチカチカするなどの副作用があるので、まず使われない。
Event loop: microtasks and macrotasks¶
<https://javascript.info/event-loop> のノート。
ブラウザー JavaScript の実行フローはイベントループに基づく。
Event Loop¶
JavaScript でもイベントループの概念は他言語のそれとまったく同じようだ。ほとんどの時間は何もしていない。スクリプト、ハンドラー、イベントがアクティブになった場合に実行されるだけだ。
イベントループが持つタスクキューを v8 用語でマクロタスクキューと呼ぶ。
エンジンがタスクを実行している間、レンダリングは決して行われない。タスクの処理時間の長さは問題にならない。タスクが完了してからでないと DOM に変更が加えられない。
タスクに時間がかかりすぎると、ブラウザーは他のタスクができなくなる。そのため、時間が経つと Page Unresponsive などの警報を出し、ページ全体でタスクを終了させようと言う。
Use-case 1: splitting CPU-hungry tasks¶
CPU を食うタスクを小分けにしてキューに押し込む手法を考察している。
setTimeout() で順次部分をキューに押し込むことで、タスク全体が画面をブロックするのを緩和する。
setTimeout()は最低でも 4ms 経過しないとコールバックをしない。そのため、スケジュールのタイミングにコツがいる。
Use case 2: progress indication¶
時間がかかるタスクを小分けにしないと、画面を更新する次の機会はタスク全体の終了後なので、プログレスバーのような進捗表示をすることができない。
Use case 3: doing something after the event¶
targ.dispatchEvent(event) 呼び出しを setTimeout() でラップするパターンのおさらい。
Macrotasks and Microtasks¶
マクロタスクの他に、マイクロタスクという概念もある。
マイクロタスクは私たちのコードからだけ発生する。それらは通常
Promiseが生成する。.then()/.catch()/.finally()ハンドラーの実行がマイクロタスクになる。マイクロタスクは
awaitの舞台裏としても使われる。特殊な関数
queueMicrotask(func)がある。関数funcの実行をマイクロタスクキューに待つ。
すべてのマクロタスクの直後に、エンジンはマイクロタスクキューからすべてのタスクを実行する。他のマクロタスク、レンダリング、その他の何かを実行する前に、マイクロタスクを実行する。
マイクロタスクはすべて、他のイベント処理、レンダリング、その他マクロタスクが行われる前に完了する。これは重要だ。マイクロタスク間でアプリケーション環境が基本的に同じであることが保証される。マウス座標の変更や新しいネットワークデータなどはない。
ある関数を非同期で(現在のコードの後で)実行したいが、変更がレンダリングされたり新しいイベントが処理される前に実行したい場合は、関数
queueMicrotaskを使ってスケジュールする。プログレスバーのコードで
setTimeoutの代わりにqueueMicrotaskを使うと、数字の表示が最後の一度きりになる。
Tasks¶
What will be the output of this code?¶
直接実行して結果を確認してからそれを解答とするのは論外だ。