JavaScript/DOM勉強メモ

超個人的メモです。
jQueryなしにJavaScriptを使いたい主義で。

url先のレスポンス(HTMLでもJSONでも返してくるもの)を取得する

function getSrc(url) {
  var req = new XMLHttpRequest();
  req.open("GET", url, false);  // 第3引数がfalseなので同期通信
  req.send(null);               // 通信開始
  return req.responseText;
}

返値:String

HTMLをDOMにする

html要素の中に入れる
var html = <HTML文字列>
var domParser = new DOMParser();
var parsedHtml = domParser.parseFromString(html, "text/html").documentElement
div要素の中に入れる
var html = <HTML文字列>
var dom = document.createElement("div");
dom.innerHTML = html;

DOMインターフェースのヒエラルキー(とは言わないのかもしれないけど)

コメント、テキスト、属性(Attribute)、要素(Element)、Document などほとんどのインターフェースは Node から派生する。

DOM要素を取得

要素を指定して取得

(リンク先はMozilla Developer Network)

XPathResultを扱いやすく

XPathResult は扱いにくい(単一と複数で振る舞いが違うしforEachも使えない)ので、ラップした関数を使うと便利そう。

document.xpath = function(expression) {
  ret = document.evaluate(expression, document, null, XPathResult.ANY_TYPE, null);
  switch(ret.resultType) {
  case  1: return ret.numberValue;  // NUMBER_TYPE
  case  2: return ret.stringValue;  // STRING_TYPE
  case  3: return ret.booleanValue; // BOOLEAN_TYPE
  case  4:                          // UNORDERED_NODE_ITERATOR_TYPE
  case  5: a=[];                    // ORDERED_NODE_ITERATOR_TYPE
           while(e=ret.iterateNext()) { a.push(e); };
           return a;                // 複数要素を要素とする配列を返す
  default: return ret;
  }
}

document.evaluate でXPATH する具体的サンプル - それマグで!

特定要素からの相対位置で指定して取得

参考図:

注意すべきこと:テキストノード及びコメントノードも指定に含まれる場合がある

この図の場合、 document.getElementsByTagName("body").firstChild の返値は h1 タグではなく "\n␣␣" になる。document.getElementsByTagName("body").firstElementChild にすれば h1 タグが返る。

テキストノードなどを含む指定

返値には要素( Element オブジェクト)以外のテキストノード・コメントノードも含まれる。

テキストノードなどを含まない指定

返値には要素( Element オブジェクト)のみが含まれる。

Element Traversal Specification | W3C Recommendation

DOMの属性を取得

対象要素targetのid属性を取得
target.id;
target.getAttribute("id");
target.attributes["id"];
target.attributes.item("id"); // 本来はインデックス番号のみ
target.attributes.getNamedItem("id");

DOMに要素を追加(innerHTMLの例は省く)

要素作成
document.createElement(<タグ名>);
テキスト要素作成
document.createTextNode(<テキスト>);
textNode = document.createTextNode(null);
textNode.nodeValue = <テキスト>; // 空要素に追加。 nodeValue プロパティを変更すると反映される
elem = document.createElement(<タグ名>);
elem.textContent = <テキスト>; // 要素elem の中身(子要素)をテキストに変更する(元々の子要素は消える)
ドキュメントの断片を作成
document.createDocumentFragment();
対象要素targetにid属性"unique"を設定
target.id = "unique";
target.setAttribute("id", "unique");

後者の参考:element.setAttribute - Web API | MDN
なおここには「現在の値にアクセスしたり、変更したりするにはプロパティを使用すべきです。」と書いてあるので、前者のようにプロパティでのアクセスが出来る場合、後者は使わないほうが良さそうですね。

要素itemを対象要素targetの末尾に追加
target.appendChild(item);
target.insertBefore(item);
target.insertBefore(item, null);
要素itemを対象要素targetのなかで内部要素innerTargetの前に追加
target.insertBefore(item, innerTarget);

要素の置き換え

(Node.replaceChild について追記予定。 ChildNode.replace() というのもあるらしい。)

要素の削除

子要素の削除

(Node.removeChild について追記予定)

子要素全てを削除

子要素の先頭要素が無くなるまで削除ループを続ける。

var myNode = document.getElementById("foo");
while (myNode.firstChild) {
    myNode.removeChild(myNode.firstChild);
}

参考:Remove all child elements of a DOM node in JavaScript? - Stack Overflow

var myNode = document.getElementById("foo");
while (myNode.hasChildNodes()) {
    myNode.removeChild(myNode.firstChild);
}

参考:基本から学ぶHTML5+JavaScript iPhone/Android対応 スマートフォンアプリの作り方: クジラ飛行机, 土井 毅 p.141

自要素の削除

(ChildNode.remove() について追記予定)

イベントハンドラ

ハンドラ:イベントが生じたときに実行する関数(やメソッド)
参考:イベントハンドラとイベントリスナの用語の使い分けを教えてください|teratail

登録
  • DOM0で設定

element をクリックすると関数 Bar を実行

element.onclick = Bar;

同様の設定を行うと上書きされる。

  • DOM2で設定

element をクリックすると関数(もしくはhandleEvent()メソッドを実装するオブジェクト)である listener を実行

element.addEventListener("click", listener);

同様の設定を行うと重複設定される。
削除するには

element.removeEventListener("click", listener)
呼び出し
  • DOM0
foo.onclick = function() {}; //匿名関数
// 呼び出し
foo.onclick();

当然ながら、イベントに登録している関数自身を呼び出しても同じ効果が得られる

foo.onclick = Bar;
// 関数の呼び出し
Bar();
  • DOM2
foo.addEventListener("click", Bar);
// 関数の呼び出し
Bar();

ただし、HTMLElement.click() / HTMLElement.focus() / HTMLElement.blur()イベントハンドラを呼び出せる。

foo.addEventListener("click", Bar);
foo.click();
  • thisについて

ハンドラが登録されたオブジェクト(らしい
(追記予定)

画像の真のサイズの取得

画像を load し終えてから addEventListener で取得。 DOMContentLoaded では取得出来ない。

<body>
<script>
var listener = function(evt){
  var image = document.getElementById("image");
  var out = [];
  out.push(" width: " + image.naturalWidth);
  out.push("height: " + image.naturalHeight);
  console.log(out.join("\n"));
};
// window.addEventListener("DOMContentLoaded", listener);
window.addEventListener("load", listener);
</script>
<img id="image" src="http://ほにゃらら" />
</body>

参考:画像の naturalWidth と naturalHeight とれるかな : 自作自演

インターフェース

継承に関する用語。JavaとかC#あたりで使われる。

クラスがオブジェクトの実装を指定するものであるのに対して,インタフェースはオブジェクトの外見(どのようなメソッドを持つかなど)だけを指定します。
まつもと直伝 プログラミングのオキテ - まつもと直伝 プログラミングのオキテ 第3回(3):ITpro

Nodeインターフェース

Node インターフェースには、すべての「DOM オブジェクト」で利用できる、基本的な機能がまとまっています。
「ノードインターフェース」を使用すると、木構造の親子関係を構築することができます。
JavaScriptプログラミング講座【ドキュメントオブジェクトモデル(DOM)について】

コンストラク

関数 A がコンストラクタの場合。

function A(){};
var a = new A(); // new A でも大丈夫なようだが、関数だから () を付けた方がいいのかな
A.prototype.test = "test"
a.test // "test" // インスタンス生成後でも反映されるのは「暗黙の参照」が働くため
prototypeの注意事項

なお,prototype拡張による具体的な問題点として,for inを用いた際にそのプロパティが列挙されてしまうという問題があります。
配列に対してfor inは使わないほうがよいというのも覚えておくべきことですが,ネイティブオブジェクトのprototype拡張はどこかでfor inが使われているんじゃないかと注意しなければいけなくなるのでお勧めできません。
 :(中略)
for inを通常は用いることがないであろうString,Number,Functionなどのネイティブオブジェクトであれば,prototypeを拡張しても問題が出にくいため,積極的に拡張してもよいでしょう。
第14回 プロトタイプと継承:これでできる! クロスブラウザJavaScript入門|gihyo.jp … 技術評論社

prototypeと__proto__

コンストラクタの prototype と、そのコンストラクタから作られたインスタンスの __proto__ は等しい。

継承

B の親が A の場合。

b.__proto__ === B.prototype
b.__proto__.__proto__ === A.prototype
B.prototype.__proto__ === A.prototype
B.prototype.__proto__ === a.__proto__ // <- a.__proto__ === A.prototype
B.prototype === a // new A()

を実現すれば良い。(参考:第15回 プロトタイプと継承#2:これでできる! クロスブラウザJavaScript入門|gihyo.jp … 技術評論社

function A(){};
function B(){};
B.prototype = new A();

配列っぽいけど配列じゃ無い(=lengthプロパティはある)オブジェクトobjで配列のメソッドを使いたい

forEachを使いたい

もちろん map, filter, every/some, reduce/reduceRight でも同様に書けます。

Array.prototype.forEach.call;
Array.prototype.forEach.apply;
> var arrMeta = docObj.getElementsByTagName('meta'); 
    // 返値の NodeList オブジェクトは Array オブジェクトじゃないので forEach を使えない
> Array.prototype.forEach.call( arrMeta, function(obj) { console.log( obj.getAttribute("property") ) } );
> Array.prototype.forEach.apply( arrMeta, [function(obj) { console.log( obj.getAttribute("property") ) }] ); 
    // call とほぼ同じですが apply の場合は第2引数を配列にする必要があるようです
Arrayにしてしまうfunction

slice() を使うと配列そのものが返る。

function toArray(obj) {
  return Array.prototype.slice.call(obj);
}

たとえばdocument.getElementsByTagNameで取得することができるNodeListは動的なものなりますので、for文を用いてループをさせると多少遅くなってしまいます。なのでtoArray(document.getElementsByTagName('a'))のようにあらかじめしておけば、速度も速くなりますし、またforEachやmapといった配列を操作するのに便利なメソッドをつかうことが可能になり、コード全体の見通しも比較的 良くなるのではないでしょうか。

オブジェクトobjのコンストラクタを知りたい

Object.prototype.toString.call(obj);
Object.prototype.toString.apply(obj);
    // 全く同じ
> Object.prototype.toString.apply(arrMeta);
< "[object NodeList]"
返値の意味

全てのオブジェクトは toString メソッドを持ち、オブジェクトが文字列値として表されるべきときや、文字列が期待される構文で参照されたときに自動的に呼び出されます。デフォルトで、toString メソッドは Object の子孫にあたるあらゆるオブジェクトに継承されています。このメソッドがカスタムオブジェクト中で上書きされていない場合、toString は [object type] を返します( type はそのオブジェクトの型)。

オブジェクトのプロパティ一覧を列挙するには

Object.keys() を使うのが一番簡単っぽい。

Object.keys()

概要

与えられたオブジェクト自身に存在する列挙可能なプロパティの配列を、for-in ループで提供されるものと同じ順番で返します (for-in ループとの違いは、for-in ループではプロトタイプチェインのプロパティも列挙することです)。

var arr = ["a", "b", "c"];
alert(Object.keys(arr)); // "0,1,2" とアラート表示

// array like object
var obj = { 0 : "a", 1 : "b", 2 : "c"};
alert(Object.keys(obj)); // "0,1,2" とアラート表示

// getFoo は列挙可能ではないプロパティ
var my_obj = Object.create({}, { getFoo : { value : function () { return this.foo } } });
my_obj.foo = 1;

alert(Object.keys(my_obj)); // foo のみがアラート表示

列挙可能ではないものを含むすべてのプロパティを取得したい場合は、Object.getOwnPropertyNames() をご覧ください。