JavaScript で多めの要素追加は何が早いんだろね?比較してみたよ!

JavaScript
最近は JavaScript スキー!でスキルアップしたいと思っています!

はじめに!

去年ぐらいから actyway では JavaScript な内容の記事が多かったりします。過去に書いた JavaScript を書き直したいと思ったりするのですが、根本的に分かっていない部分などが多いので一つ一つ地道に検証して体で覚えようと思っています。今回は要素(大量、多め?)の追加で、何が早いのか比較検証してみたいと思います。

innerHTML, appendChild, insertBefore それと DocumentFragment を使っています。ループは、setIntervalfor で試しています。

速度比較!

  • その1 … setinterval で8タイプのテスト
  • その2 … for で8タイプのテスト

その1!

JavaScript 速度チェック8タイプ

JavaScript 要素の追加速度比較チェック!その1 : actyway
レンダリングの感じも見たかったので、setInterval で速度のテスト8タイプ用意しました、別ページにて試せます。

innerHTML の速度確認結果

チェックしたい要素をクリックすると、ツツツツーっと小さい四角が表示された後に結果も表示されます。この例では、innerHTMLdiv500個入れてみて、開始と終了のタイムスタンプを取得してその差を結果に表示しています。ループはレンダリングの感じも見たかったので、setInterval(,0) で回しています。2692(ミリ秒)かかってるって事ですね!

1. setInterval

d.getElementById("test1").addEventListener('click',function(){
	this.innerHTML = '';
	var count = 0,
	start = new Date().getTime(),
	loop = setInterval(function(){
		if(count == max){
			var end = new Date().getTime();
			clearInterval(loop);
			d.getElementById("test1").innerHTML += '<p>開始: '+start+'</p><p>終了: '+end+'</p><p>結果: '+(end-start)+'</p>';
		}
		count++;
	},0);
},false);

setInterval(,0) だけ実行した場合の結果が表示されます。ループだけにどれだけ時間かかってるか、目安的に…w

2. innerHTML1

d.getElementById("test2").addEventListener('click',function(){
	this.innerHTML = '';
	var count = 0,
	start = new Date().getTime(),
	loop = setInterval(function(){
		d.getElementById("test2").innerHTML += '<div></div>';
		if(count == max){
			var end = new Date().getTime();
			clearInterval(loop);
			d.getElementById("test2").innerHTML += '<p>開始: '+start+'</p><p>終了: '+end+'</p><p>結果: '+(end-start)+'</p>';
		}
		count++;
	},0);
},false);

先ほど上で説明した物です。innerHTMLdiv 500個追加!

3. innerHTML2

d.getElementById("test3").addEventListener('click',function(){
	this.innerHTML = '';
	var count = 0,
	start = new Date().getTime(),
	dots = "",
	loop = setInterval(function(){
		dots += '<div></div>';
		if(count == max){
			d.getElementById("test3").innerHTML = dots;
			var end = new Date().getTime();
			clearInterval(loop);
			d.getElementById("test3").innerHTML += '<p>開始: '+start+'</p><p>終了: '+end+'</p><p>結果: '+(end-start)+'</p>';
		}
		count++;
	},0);
},false);

2の結果が遅かったので、中身を一旦変数に入れてから最後に一度だけ innerHTML で追加してみました。

4. appendChild

d.getElementById("test4").addEventListener('click',function(){
	this.innerHTML = '';
	var count = 0,
	start = new Date().getTime(),
	loop = setInterval(function(){
		var dot = d.createElement("div");
		d.getElementById("test4").appendChild(dot);
		if(count == max){
			var end = new Date().getTime();
			clearInterval(loop);
			d.getElementById("test4").innerHTML += '<p>開始: '+start+'</p><p>終了: '+end+'</p><p>結果: '+(end-start)+'</p>';
		}
		count++;
	},0);
},false);

2の appendChild バージョンです。

5. insertBefore

d.getElementById("test5").addEventListener('click',function(){
	this.innerHTML = '';
	var count = 0,
	start = new Date().getTime(),
	loop = setInterval(function(){
		var dot = d.createElement("div");
		d.getElementById("test5").insertBefore(dot,d.getElementById("test5").firstChild);
		if(count == max){
			var end = new Date().getTime();
			clearInterval(loop);
			d.getElementById("test5").innerHTML += '<p>開始: '+start+'</p><p>終了: '+end+'</p><p>結果: '+(end-start)+'</p>';
		}
		count++;
	},0);
},false);

2の insertBefore バージョンです。

6. DocumentFragment1

d.getElementById("test6").addEventListener('click',function(){
	this.innerHTML = '';
	var count = 0,
	start = new Date().getTime(),
	union = d.createDocumentFragment(),
	loop = setInterval(function(){
		var dot = d.createElement("div");
		union.appendChild(dot);
		if(count == max){
			d.getElementById("test6").appendChild(union);
			var end = new Date().getTime();
			clearInterval(loop);
			d.getElementById("test6").innerHTML += '<p>開始: '+start+'</p><p>終了: '+end+'</p><p>結果: '+(end-start)+'</p>';
		}
		count++;
	},0);
},false);

appendChild の追加先を DocumentFragment で試してみました。

7. DocumentFragment2

d.getElementById("test7").addEventListener('click',function(){
	this.innerHTML = '';
	var count = 0,
	start = new Date().getTime(),
	union = d.createDocumentFragment(),
	dot = d.createElement("div"),
	loop = setInterval(function(){
		var dotc = dot.cloneNode(false);
		union.appendChild(dotc);
		if(count == max){
			d.getElementById("test7").appendChild(union);
			var end = new Date().getTime();
			clearInterval(loop);
			d.getElementById("test7").innerHTML += '<p>開始: '+start+'</p><p>終了: '+end+'</p><p>結果: '+(end-start)+'</p>';
		}
		count++;
	},0);
},false);

div を事前に用意してそれを cloneNode して DocumentFragmentappendChild しています。単純に cloneNode ってどうなんだろうと思った為に。

8. DocumentFragment3

d.getElementById("test8").addEventListener('click',function(){
	this.innerHTML = '';
	var count = 0,
	start = new Date().getTime(),
	union = d.createDocumentFragment(),
	dot = d.createElement("div");
	union.appendChild(dot);
	var loop = setInterval(function(){
		var dotc = union.firstChild.cloneNode(false);
		union.appendChild(dotc);
		if(count == max){
			d.getElementById("test8").appendChild(union);
			var end = new Date().getTime();
			clearInterval(loop);
			d.getElementById("test8").innerHTML += '<p>開始: '+start+'</p><p>終了: '+end+'</p><p>結果: '+(end-start)+'</p>';
		}
		count++;
	},0);
},false);

div を事前に用意してそれを DocumentFragmentappendChild で追加した物をさらに cloneNode して DocumentFragmentappendChild しています。

その1の結果!

Mac から試したので IE は含みませんが以下のブラウザで試しました。拡張機能、アドオンなどは全て無効でノーマルに近い状態で、一応キャッシュなども全てクリアしました。

  • Chrome 26.0.1410.65
  • Firefox 20.0
  • Opera 12.15 build 1748
  • Safari 5.1.8 (6534.58.2)
Chrome Firefox Opera Safari 平均
1 2056.5 1999.7 2036.7 2025.1 2029.5
2 2532.7 5245.9 3796.6 2655.1 3557.575
3 2065 2002 2039.4 2030.4 2034.2
4 2284.2 2036.5 4127.5 2176.6 2656.2
5 2390.6 2477.7 4127.8 2059.2 2763.825
6 2072.6 2039.4 2037.6 2030.8 2045.1
7 2075.8 2021.6 2038.3 2028.7 2041.1
8 2060.9 2068 2039.6 2028.4 2049.225

8種類を手動で実行&リロードを10回試してみての平均値です。

  • Firefox で 2. innerHTML1innerHTML += ‘<div></div>’; が遅いみたいですね。
  • 3. innerHTML2 が大健闘じゃないですか?innerHTML を使うなら一旦変数に全部入れてから一度だけ innerHTML するのが良い。
  • 4. appendChildappendChild5. insertBeforeinsertBefore はそこまで違わない、6〜8の結果から DocumentFragment を使った方が良い。
  • 6〜8はほとんど同じ。

…等々思いました。あと、なんか Firefox だと読み込み直後と数秒置いてから実行するのとでは結果が違ってきてる印象を受けました。要素数の変化で結果がどの様に変わってくるのかも気になるところですが手動では大変ですね! setInterval で回して要素追加する機会ってあまりなさそうなので、その1はこれで終わります。

その2!

Chrome の Console で JavaScript

その2は、setInterval ではなく for で回します。「about:blank」で コンソールを開いて直接 JavaScript を実行してみる事にしました。

Chrome, Safari なら Developer Tools の Console から、Firefox では Web コンソールを開いてスクラッチパッドから、Opera では Dragonfly の Console から実行してみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
(function(d){
	var max = 500,loop = 10,i,l,start,end,
	css = d.createElement("link");
	css.type = "text/css";
	css.rel = "stylesheet";
	css.href = "http://actyway.com/demo/javascript.8490.2.css";
	d.getElementsByTagName("head")[0].appendChild(css);
 
	function resetVariable (){
		d.body.innerHTML = '';
		end = '';
		start = new Date().getTime();
	}
 
	/* 1. setInterval */
	setTimeout(function(){
		for(l = 0; l < loop; l++){
			resetVariable();
			for(i = 0; i < max; i++){
				if(i == (max-1)){
					end = new Date().getTime();
					console.log('1-'+l+': 開始: '+start+' 終了: '+end+' 結果: '+(end-start));
				}
			}
		}
	},0);
 
	/* 2. innerHTML1 */
	setTimeout(function(){
		for(l = 0; l < loop; l++){
			resetVariable();
			for(i = 0; i < max; i++){
				d.body.innerHTML += '<div></div>';
				if(i == (max-1)){
					end = new Date().getTime();
					console.log('2-'+l+': 開始: '+start+' 終了: '+end+' 結果: '+(end-start));
				}
			}
		}
	},0);
 
	/* 3. innerHTML2 */
	setTimeout(function(){
		for(l = 0; l < loop; l++){
			resetVariable();
			var dots = "";
			for(i = 0; i < max; i++){
				dots += '<div></div>';
				if(i == (max-1)){
					d.body.innerHTML = dots;
					end = new Date().getTime();
					console.log('3-'+l+': 開始: '+start+' 終了: '+end+' 結果: '+(end-start));
				}
			}
		}
	},0);
 
	/* 4. appendChild */
	setTimeout(function(){
		for(l = 0; l < loop; l++){
			resetVariable();
			for(i = 0; i < max; i++){
				var dot = d.createElement("div");
				d.body.appendChild(dot);
				if(i == (max-1)){
					end = new Date().getTime();
					console.log('4-'+l+': 開始: '+start+' 終了: '+end+' 結果: '+(end-start));
				}
			}
		}
	},0);
 
	/* 5. insertBefore */
	setTimeout(function(){
		for(l = 0; l < loop; l++){
			resetVariable();
			for(i = 0; i < max; i++){
				var dot = d.createElement("div");
				d.body.insertBefore(dot,d.body.firstChild);
				if(i == (max-1)){
					end = new Date().getTime();
					console.log('5-'+l+': 開始: '+start+' 終了: '+end+' 結果: '+(end-start));
				}
			}
		}
	},0);
 
	/* 6. DocumentFragment1 */
	setTimeout(function(){
		for(l = 0; l < loop; l++){
			resetVariable();
			var union = d.createDocumentFragment();
			for(i = 0; i < max; i++){
				var dot = d.createElement("div");
				union.appendChild(dot);
				if(i == (max-1)){
					d.body.appendChild(union);
					end = new Date().getTime();
					console.log('6-'+l+': 開始: '+start+' 終了: '+end+' 結果: '+(end-start));
				}
			}
		}
	},0);
 
	/* 7. DocumentFragment2 */
	setTimeout(function(){
		for(l = 0; l < loop; l++){
			resetVariable();
			var union = d.createDocumentFragment(),
			dot = d.createElement("div");
			for(i = 0; i < max; i++){
				var dotc = dot.cloneNode(false);
				union.appendChild(dotc);
				if(i == (max-1)){
					d.body.appendChild(union);
					end = new Date().getTime();
					console.log('7-'+l+': 開始: '+start+' 終了: '+end+' 結果: '+(end-start));
				}
			}
		}
	},0);
 
	/* 8. DocumentFragment3 */
	setTimeout(function(){
		for(l = 0; l < loop; l++){
			resetVariable();
			var union = d.createDocumentFragment(),
			dot = d.createElement("div");
			union.appendChild(dot);
			for(i = 0; i < max; i++){
				var dotc = union.firstChild.cloneNode(false);
				union.appendChild(dotc);
				if(i == (max-1)){
					d.body.appendChild(union);
					end = new Date().getTime();
					console.log('8-'+l+': 開始: '+start+' 終了: '+end+' 結果: '+(end-start));
				}
			}
		}
	},0);
})(document);

スクリプトはこんな感じで、その1の setIntervalfor にしました。一度の実行で全部の結果が分かるようにしました。setTimeout(,0) を使っているのは順番に実行したかった為です。

その2の結果!

Chrome Firefox Opera Safari 平均
1 0.2 0 0.3 0.1 0.15
2 5387.8 458.7 593.4 3095.1 2383.75
3 2.9 0.4 2.2 9.7 3.8
4 1.7 6.6 6 9.7 6
5 0.9 7.6 8.1 9.8 6.6
6 1 3.3 3.9 9.5 4.425
7 1.3 2 3 9.1 3.85
8 1.6 2.8 3.5 9.1 4.25

ざっくりだと、その1の結果と印象は変わらないですよね〜?

Chrome と Safari からだと 2. innerHTML1 の部分がすごく遅いんです。CPU をものすごく使っているみたいでブラウザが反応しなくなる程に…。(わたしの環境だけでしょうか? Chrome だと5000超えてしまう。)そして理由が分かっていません…w

まとめ!

2. innerHTML1 は、使わない方が良い。他はあまり変わらないので神経質になる程でも無いって印象でした。個人的には、3. innerHTML2 みたいな方法は使いやすいので知れて嬉しかったです!

要素数が500個だけだったのと、回数も10回だけだったのでもう少し増やしたりして試したら印象は違ってきたのかもしれません。機会がありましたらまた試してみたいと思います。参考になりそうでしたら幸いです。

間違いやもっと良い方法がありましたら教えていただけたらと思います! @actywav までお願いしま〜す。