🧊

compareDocumentPositionとビットマスクとビット演算

部屋とYシャツと私みたいなタイトルになった・・。

わかったのかわかってないのか、とりあえず調べたことをメモっておくための記事。

事の発端

とある偉人のコードを見てた時に見つけた以下の行。

var DOCUMENT_POSITION_ANCESTOR = global.Node.DOCUMENT_POSITION_PRECEDING | global.Node.DOCUMENT_POSITION_CONTAINS;
var DOCUMENT_POSITION_DESCENDANT = global.Node.DOCUMENT_POSITION_FOLLOWING | global.Node.DOCUMENT_POSITION_CONTAINED_BY;

どゆこと・・?

もしかして || って書こうとしてて忘れたんかな・・・って、
まあそんなわけ万が一にもないですよねー、ということで、コイツの正体を追っていくことに。

コレで何してるか

もちろん変数にとってあるので使う目的があるはず。
それを見てみると・・・、

pos = this.target.compareDocumentPosition(touchInfo.target);
if (pos === DOCUMENT_POSITION_ANCESTOR || pos === DOCUMENT_POSITION_DESCENDANT) {
    // ...
}

ふむ。


・・ふむ。

node.compareDocumentPosition(otherNode)とは

参考: Node.compareDocumentPosition() - Web API Interfaces | MDN

これはなにかというと、

var head = document.getElementsByTagName('head').item(0);
head.compareDocumentPosition(document.body); // => 4

というように、ノード間の位置関係を取得するメソッド。

上記の例でいうと、head と body はHTMLの構造としては以下で、

<html>
  <head></head>
  <body></body>
</html>

返ってきた 4 は、DOCUMENT_POSITION_FOLLOWING の 4 らしく、
つまり、head からすると body は、後にある(=FOLLOWING)関係ってことになる。

The return value is a bitmask with the following values:

さっきの 4 についてもっと詳しく。
compareDocumentPositionの返り値は以下で、ビットマスクであるらしい。

a.compareDocumentPosition(b)のとき、
name value 意訳
DOCUMENT_POSITION_DISCONNECTED 1 bはaと同一tree上にない
DOCUMENT_POSITION_PRECEDING 2 bはaの前にある
DOCUMENT_POSITION_FOLLOWING 4 bはaの後にある
DOCUMENT_POSITION_CONTAINS 8 bはaを含んでいる
DOCUMENT_POSITION_CONTAINED_BY 16 bはaに含まれている
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC 32 実装次第(なんだと...)


さて、さっきの例では 4 が返ってきたけども、以下の例だと違う。

var head  = document.getElementsByTagName('head').item(0);
var title = document.getElementsByTagName('title').item(0);

head.compareDocumentPosition(title); // => 20

これはHTML構造でみると、

<html>
  <head>
    <title></title>
  </head>
</html>

さっきの表とてらしてぱっと見る感じ、

  • titleはheadより、後ろにある
  • titleはheadに、含まれている

なので、20。

えーっと、それはなんで?ってことで次。

ビットマスクとビット演算と

さっきの返り値 20 は、10進数の 20 。
ただコレはビットマスクで表した結果であると書いてありました。

ビットマスクについてはまず以下の記事を。

参考: ビット演算子 - JavaScript | MDN

2進数で1|0のフラグを複数組み合わせて単一の条件を表現するって感じかね。

さっきの例でいうと、

// この結果が 20
head.compareDocumentPosition(title); // => 20

// 20はビットマスクで20なので、
// どういうビットの並びかを調べるには、2進数にする
(20).toString(2); // => 10100

// 10100は、10000 と 100 から成るビットマスクってことがわかったので、

// 10000 を10進数に戻す
parseInt(10000, 2); // => 16

// 100 も10進数に戻す
parseInt(100, 2); // => 4

さっきの表でみると

  • 4: DOCUMENT_POSITION_FOLLOWING (=後にある)
  • 16: DOCUMENT_POSITION_CONTAINED_BY (=含まれている)

なので、最初の予想と同じになる、って感じ。

ビット演算の | は

a | b でどっちかが 1 なら 1 になるので、

var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000
// -----------------------
// AかBかC ------> 0111

var mask = FLAG_A | FLAG_B | FLAG_C; // => 0111なので 7

となる。

ここで最初のコードに

var DOCUMENT_POSITION_ANCESTOR = global.Node.DOCUMENT_POSITION_PRECEDING | global.Node.DOCUMENT_POSITION_CONTAINS;
var DOCUMENT_POSITION_DESCENDANT = global.Node.DOCUMENT_POSITION_FOLLOWING | global.Node.DOCUMENT_POSITION_CONTAINED_BY;

さっきの表とてらしてみる

var DOCUMENT_POSITION_ANCESTOR = 2 | 8; // => 10
var DOCUMENT_POSITION_DESCENDANT = 4 | 16; // => 20

つまり、このコードの意味は、

pos = this.target.compareDocumentPosition(touchInfo.target);
if (pos === 10 || pos === 20) {
    // ...
}

ここでいう pos が、

  • 4: DOCUMENT_POSITION_FOLLOWING (=後にある)
  • 16: DOCUMENT_POSITION_CONTAINED_BY (=含まれている)

の組み合わせで 20 のとき

もしくは、

  • 2: DOCUMENT_POSITION_PRECEDING (=前にある)
  • 8: DOCUMENT_POSITION_CONTAINS (=含んでいる)

の組み合わせで 10 のとき

っていう判別をしていることになる・・と。


あーそーゆことね、完全に理解した!