🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

2回答

7944閲覧

JavaScriptで処理速度を向上させるコードの工夫

risu

総合スコア6

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

1グッド

8クリップ

投稿2019/12/13 00:25

編集2019/12/13 02:01

###実現したいこと

以下の流れでAJAXを使ってHTMLを挿入するのですが、この速度を向上させたいと考えています。
###HTML挿入の流れ
HTML挿入の流れはこうです。

記事一覧にはタイトルとサムネイルが表示されており、それをクリックします。

そのとき通信はせず、まず先にタイトルとサムネイルを表示します。さらにそれ以外の部分の外郭HTMLも表示し、「読み込み中」とします。

そしてAJAXでその他のデータを取得し、できたらその中身を挿入する。という流れです。
イメージ説明

###速度の問題
AJAXの前にすでにあるデータを表示したり、外郭のHTMLを表示したりするというのは体感速度を上げる狙いです。

しかしHTMLの挿入速度に問題があります。

上図のように「外郭を先に表示し、データは後で挿入」でなく、「AJAXでの取得が終わったそのときに、データが挿入されたHTMLを表示」とした方が、処理が早いようなのです。

その計測が次のソースコードになります。

###該当のソースコード
ご覧のように「keisoku_1();」と「keisoku_2();」で計測しました。

「keisoku_1();」の方は上図のように「外郭を先に表示し、データは後で挿入」という方法です。

「keisoku_2();」の方は「AJAXでの取得が終わったそのときに、データが挿入されたHTMLを表示」という方法です。

それぞれ実行すると分かるとおり、「keisoku_2();」の方が圧倒的に速いのです。

js

1 2/* 3keisoku_1(); 4外郭のHTMLを取得し、後からデータを入れる方法を計測 5----------------------------------------------*/ 6// 計測を実行 7keisoku_1(); 8function keisoku_1(){ 9 10 var time_before = new Date().getTime(); 11 12 var lis = ''; 13 for ( var i = 0; i < 1000; i++ ) { 14 var data = { num:i, ttl:'hello_'+i }; 15 var html = get_outline_html( i ); 16 var html = update_outline_html( html, data ); 17 lis += html; 18 } 19 $( 'body' ).html( lis ); 20 21 var time_after = new Date().getTime(); 22 console.log( 'keisoku_1() = ' + parseInt(time_after - time_before) ); 23} 24 25// 外郭のHTMLを取得 26function get_outline_html( i ){ 27 return ` 28 <div class="box num${i}"> 29 <p class="ttl"></p> 30 <p class="num"></p> 31 </div>`; 32} 33 34// 後からデータを入れる 35function update_outline_html( html, data ){ 36 var $html = $( html ) 37 .find( '.ttl' ).text( data.ttl ) 38 .find( '.num' ).text( data.num ); 39 return $html[0]; 40} 41 42 43/* 44keisoku_2(); 45データを入れたHTMLを取得する方法を計測 46----------------------------------------------*/ 47// 計測を実行 48keisoku_2(); 49function keisoku_2(){ 50 51 var time_before = new Date().getTime(); 52 53 var lis = ''; 54 for ( var i = 0; i < 1000; i++ ) { 55 var data = { num:i, ttl:'hello_'+i }; 56 var html = get_all_html( i, data ); 57 lis += html; 58 } 59 $( 'body' ).html( lis ); 60 61 var time_after = new Date().getTime(); 62 console.log( 'keisoku_2() = ' + parseInt(time_after - time_before) ); 63} 64 65// データを入れたHTMLを取得 66function get_all_html( i, data ){ 67 return ` 68 <div class="box num${i}"> 69 <p class="ttl">${data.ttl}</p> 70 <p class="num">${data.num}</p> 71 </div>`; 72} 73

###質問
そこで質問ですが、上述した「HTML挿入の流れ」はそのままで、もう一歩速度を向上させられるようなソースコードの工夫などございましたらご教授頂けますでしょうか。

当方JavaScript初心者のため上のソースコードにおかしな点などあるやもしれませんので、もし意図が伝わりにくい等の不備がございましたらどうぞご指摘ください。

以上です。何卒、宜しくお願い申し上げます。

###改善の追記
maisumakun様のアドバイスを受けまして、以下「keisoku_3();」です。
【改善1】として「.clone()」を使い、【改善2】としてクラス付与を変更致しました。

ですが「$html」が取得できないようで、要素の挿入がされない状況になってしまいました。
困ったことにエラーも出ておらず、悪いところがわかりません。

お気づきの点ございましたらご指摘いただければと思い、ここに追記させて頂きました。

js

1 2/* 3keisoku_3(); 4maisumakun様のアドバイスを受けて 5----------------------------------------------*/ 6// 計測を実行 7keisoku_3(); 8function keisoku_3(){ 9 10 var time_before = new Date().getTime(); 11 12 var $html; 13 for ( var i = 0; i < 3; i++ ) { 14 var data = { num:i, ttl:'hello_'+i }; 15 16 if ( i == 0 ){ 17 // 【改善1】1回目だけjQeury化する 18 var html = get_outline_html( '' ); // 【改善2】iを渡してクラス付与しない 19 var $html = $( html ) 20 .find( '.ttl' ).text( data.ttl ) 21 .find( '.num' ).text( data.num ) 22 .find( '.box' ).addClass( 'num'+i ); // 【改善2】ここでクラス付与する 23 }else{ 24 // 【改善1】2回目以降は.clone()する 25 var $html = $html.clone(); 26 $html 27 .find( '.ttl' ).text( data.ttl ) 28 .find( '.num' ).text( data.num ) 29 .find( '.box' ).addClass( 'num'+i ); 30 } 31 $( 'body' ).html( $html[0] ); // $html が取得できていない様子 32 } 33 34 var time_after = new Date().getTime(); 35 console.log( 'keisoku_3() = ' + parseInt(time_after - time_before) ); 36} 37 38
taiyakix👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2019/12/13 00:36

念のため、計測結果をご提示ください
x_x

2019/12/13 06:34

Ajax による通信が一番遅いと思うのですが、コードではそんなことは関係なしにデータを受け取った後にいかに早く表示するかということしかないように見えます。そういう質問なのでしょうか?
risu

2019/12/13 06:44

大変失礼致しました。こちらのコメント欄に今気づきました。 >m.ts10806さん、thyda.eiqauさん ご指摘ありがとうございます。 >x_xさん はい、質問の意図はそうです。しかし、何か含みというか、思うことがあるご様子とお見受けしました。よろしければお聞かせいただけませんでしょうか?例えばAJAXによる通信より早い手段があるとか、そういったことございましたら、質問の意図に関わらずアドバイス戴けましたら幸いでございます。尚、特にそういった含みなどなく純粋に質問の意図のご確認であったでしたら無視して頂いて結構です。
x_x

2019/12/13 06:48

いや、「箱を表示して体感速度を上げる」のが狙いであるなら中のデータなど入れる必要などないので、いったい何を計測しているのかわからないのです。
risu

2019/12/13 07:01

箱を表示した後、その箱の中のデータを取得して挿入しなければならないのですが・・その必要がない、と仰いますと?申し訳ございません。疑問の意図をはかりかねております。 もしかして、「箱を表示できた時点で体感速度の向上は成っており、計測しているのは”箱の表示の体感速度”ではなくて、”箱の表示後に、中身が表示されるまでの体感速度”ではないか?」という疑問ですか?もしこの疑問であればまったくその通りですが、違いますよね?
x_x

2019/12/13 07:07

いやそうですね。 箱がすでにある状態から詰めるのとは効率が違うと思いますが、すでに解決したようですのでデータを受け取った後に生成するということでもいいのでしょう。
risu

2019/12/13 07:15

また何かございましたらどうぞ遠慮なく仰ってください。ありがとうございました。
guest

回答2

0

それぞれ実行すると分かるとおり、「keisoku_2();」の方が圧倒的に速いのです。

これは、keisoku_1()ではHTML1つに対して毎回DOMノードを作成してそれを書き換えているのに対して、keisoku_2()では文字列操作で済ませているからです。

ただし、そのままkeisoku_2()の形で行うことはおすすめしません。HTMLを文字列連結で作ると、不正なHTMLを構築してしまうXSSの危険があります。

keisoku_1()を高速化する方法としては、「HTMLのjQuery化は最初の1回だけにしておいて、あとは.clone()でコピーする(外側のクラスも文字列で流し込まず、jQuery操作で書き込む)」という方法があります。HTMLのパースがいちばん手間のかかる処理です。

投稿2019/12/13 00:45

maisumakun

総合スコア145975

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

risu

2019/12/13 01:00

早速のご回答をありがとうございます。毎回のDOM変換がネックで、それが「.clone()」を使うと変わりますか!理由が気になりますが、今出先で実行できないので後ほど試させて頂き、改めてご報告申し上げたいと思います。
maisumakun

2019/12/13 01:01

HTML→DOMが1回で済みます(cloneはHTMLのパースよりずっと軽い処理です)。
risu

2019/12/13 01:04 編集

なるほど、「.clone()」は良さそうですね。それぞれの処理の重さなど調べたこともありませんでした。ありがとうございます。
risu

2019/12/13 01:04

ところで勉強不足で大変恐縮ですが、XSSについて少し教えてください。 私にとってXSSとは、以下の状況においてのみありえるリスクで、以下の対策でもって完全に回避される脆弱性だという認識です。 状況 →掲示板のようにユーザーがDBに値を保存できるときにありえるリスクである。 対策 →その値はサニタイズして出力すれば、スクリプトとして実行されないので問題はない。 さて今回の質問の場合ですが、文字列で連結されたHTMLの途中に何かフロントで仕込むことは可能と思いますが、その仕込んだスクリプトが実行されるのはあくまで仕込んた本人に対してのみですよね? では、その本人が例えばどのようなスクリプトを実行すると、本人以外にどのようなリスクが想定されるのでしょうか??
maisumakun

2019/12/13 01:05

> 文字列で連結されたHTMLの途中に何かフロントで仕込むことは可能と思いますが、その仕込んだスクリプトが実行されるのはあくまで仕込んた本人に対してのみですよね? 元のデータに不適当な値が入る状況になると、見た全員に影響します。
risu

2019/12/13 01:18 編集

ご返信誠にありがとうございます。「元のデータ」というのは「AJAXでDBから引っ張ってくる値」ですよね?そして「見た全員」とは「仕込んだ本人以外の誰かを含む」ですよね?この解釈があっているとすると、やはり次のように分かりません。 DBの値が管理者しか保存できないような仕様だとすると、「HTMLを文字列連結で作ると、不正なHTMLを構築してしまうXSSの危険」は、例えばどのようなフローで想定されますか? 不正なHTMLが構築されうるのはわかります。しかしそれがどのようなフローで「仕込んだ本人以外の誰か」に影響するのでしょうか? 仕込んだ本人が自分のフロントで不正なHTMLを構築し、その本人だけにしか影響しないのではないか?という考えでおります。 お手数おかけし大変申し訳ございませんが、そのフローについて事例など教えて頂けましたらと思います。 どうぞ引き続き宜しくお願い申し上げます。
maisumakun

2019/12/13 01:22

> DBの値が管理者しか保存できないような仕様だとすると、「HTMLを文字列連結で作ると、不正なHTMLを構築してしまうXSSの危険」は、例えばどのようなフローで想定されますか? 仮に「いまの現状では問題ない」としても、「のちのち仕様が変わっていった」、あるいは「データベース自体に不正な書き込みがあった」など、実際のシステム運用では何が起きるかわかりません。 そのようなことも考えて、あえてHTMLを出力したい用事があるのでもなければ、「できるだけ安全な側になるように設計しておく」というほうが適切です。フレームワークなどを使った開発では、「HTMLエスケープがかかるのが基本で、外すときだけ特殊な書き方をしないといけない」という用になっている例も多いです。
risu

2019/12/13 01:25

>「できるだけ安全な側になるように設計しておく」というほうが適切です。 まったく仰る通りかと存じます。質問とずれてしまいましたが、アドバイス感謝申し上げます。文字列連結は避けるように致します。
risu

2019/12/13 02:04

お世話になっております。さっそくご回答の件試させて頂きましたが、質問に「改善の追記」とした通り、bodyへの要素の挿入がされない状況になってしまいました。 再三お手数おかけして誠に・・申し訳ございませんが、ご査収いただけましたら幸甚に存じます。
guest

0

ベストアンサー

テキストコントロールのみのkeisoku_2には劣りますが、
バニラJSを使った方が遥かに早くなりますよ。
createDocumentFragmentと、createElementを使っていく方法です。

javascript

1keisoku_1(); 2function keisoku_1(){ 3 4 var time_before = new Date().getTime(); 5 6 // メモリ上でDOMを保持できる、createDocumentFragmentを使用 7 var lis = document.createDocumentFragment(); 8 for ( var i = 0; i < 1000; i++ ) { 9 var data = { num:i, ttl:'hello_'+i }; 10 const li = get_outline_html( i ); 11 12 const html = update_outline_html( li, data ); 13 lis.appendChild(html); 14 } 15 document.body.appendChild(lis); 16 17 var time_after = new Date().getTime(); 18 console.log( 'keisoku_1() = ' + parseInt(time_after - time_before) ); 19} 20 21// 外郭のHTMLを取得 22function get_outline_html( i ){ 23 // バニラJSで要素を生成すれば、レンダリングせずに、メモリ上で処理するので、速い 24 const div = document.createElement('div'); 25 pTtl = document.createElement('p'), 26 pNum = document.createElement('p'); 27 div.classList.add('box'); 28 div.classList.add(`num${i}`); 29 30 pTtl.classList.add('ttl'); 31 pNum.classList.add('num'); 32 33 div.appendChild(pTtl); 34 div.appendChild(pNum); 35 return div; 36} 37 38// 後からデータを入れる 39function update_outline_html( html, data ){ 40 html.querySelector('.ttl').textContent = data.ttl; 41 html.querySelector('.num').textContent = data.num; 42 43 return html; 44}

計測結果は、12ms前後を計測していました。
質問者さんのコードだと、70ms前後なので、だいぶ早くはなっているかと。

追記:update_outline_htmlがなぜ動作しないのか

というか、そもそも、元々のkeisoku_1は動きません。
なぜなら、.findを実行した時点で、返ってくる値は、.findで見つかった要素のjQueryオブジェクトだからです。
(今回の場合はp.ttlが入る。その後にメソッドチェーンで.findしているので、当然、p.numは見つからず、$htmlには空のjQueryオブジェクトが入ってしまう)

下記に何が起きているか段階的に説明します。

javascript

1// 後からデータを入れる 2function update_outline_html( html, data ){ 3 // 最初の取得は、大枠のdiv>p.ttl+p.numという構造で取得できている 4 var $html = $( html ) 5 // この時点で、p.ttlの取得になる。なので、textメソッドも動く 6 .find( '.ttl' ).text( data.ttl ) 7 // この時点で、"p.ttlの中"の.numと探しにいく(find)ので、当然、.numは存在しないため、失敗する 8 .find( '.num' ).text( data.num ); 9 // 2回目のfindにて、要素が見つからなかったので、$htmlには、空のjQueryオブジェクトが入る 10 return $html[0]; 11}

なので、下記にする必要があります。

javascript

1// 後からデータを入れる 2function update_outline_html( html, data ){ 3 // 一回取得で止める 4 const $html = $( html ); 5 // 変数に再代入はせず、メソッドの実行だけ行う。 6 $html.find( '.ttl' ).text( data.ttl ); 7 $html.find( '.num' ).text( data.num ); 8 // $htmlには、div>p.ttl+p.numが入ったままなので、その後の処理も動作する 9 return $html[0]; 10}

もしくは下記です。

javascript

1// 後からデータを入れる 2function update_outline_html( html, data ){ 3 // メソッドチェーンでやるなら 4 const $html = $( html ) 5 .find( '.ttl' ).text( data.ttl ) 6 // 一回親(div)に遡る 7 .parent() 8 // divから探してるので、.numは見つかる 9 .find( '.num' ).text( data.num ) 10 // 最終的に親に遡って、親を返す 11 .parent(); 12 13 // $htmlには、div>p.ttl+p.numが入ってるので、その後の処理も動作する 14 return $html[0]; 15}

それと、varだったとしても、同スコープ内で、同じ変数名で、varでの再宣言は止めましょう。
メンテナンス性最悪になる上、自他共に混乱しか生みません。
やるにしても、2回目以降は、varをつけずに、再代入に留めましょう。
(別スコープなら、varで宣言し直すのはあり。ただし、varよりletの方が絶対にいい)
※let、constがなんなのか、は、本件から外れるので、ご自身でお調べください。

投稿2019/12/13 02:42

編集2019/12/13 04:35
miyabi_takatsuk

総合スコア9555

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

risu

2019/12/13 03:59

ご回答誠にありがとうございます。バニラJSとは何だ!?と調べてしまいました(笑) それにしても「createDocumentFragment」というのは初耳で、これから重宝しそうなテクニックに心躍りました。 欲を言えば「get_outline_html()」のHTMLは、バックティックで囲んだ質問の状態のように「HTMLっぽい見た目」で書いておければ初心者にとっては見やすくメンテナンスもしやすいとは思いますが。 しかしバニラJSではそれは不可能で、要素1つ1つについてまず「createElement」してから、次に「classList.add」や「setAttribute」等をする。という流れになりますよね。 メンテナンス性か速度かと悩ましいところですが、今回は速度優先とし、仰る流れを採用させて頂こうと思います。
risu

2019/12/13 04:04

>というか、そもそも、元々のkeisoku_1は動きません。 大変失礼致しました。実行するとHTMLにはkeisoku_2の方が1000件描画されるので、keisoku_1もされているだろうと思い気づきませんでした。 「.find()」はメソッドチェーンにはつかないんですね。しかし、この動かないものを動かすためにはどのように修正すべきでしょうか? 次のように「.find()」をわけた修正版としてもうまくいきませんでして、もしよろしければこちらについてもご教授いただけませんでしょうか。 // 後からデータを入れる → 修正版 function update_outline_html( html, data ){ var $html = $( html ).find( '.ttl' ).text( data.ttl ); // findをわける var $html = $( html ).find( '.num' ).text( data.num ); //findをわける return $html[0]; }
miyabi_takatsuk

2019/12/13 04:06

なるほど。 メンテナンス性に関しては最終的にはコードを使った方が上がりますが、 初心者にもわかりやすく、だと仰る通りですね。 そこはあとは天秤にかけてどちらにするか、ですね。 ちなみに、バニラでももちろんテキストでの処理もできますよ。 (途中のcreateElementを使わない方法) ただ、それすると、keisoku_2とやってること大した変わらなくなるので、 比べる意味がなくなってしまうかと思います。
miyabi_takatsuk

2019/12/13 04:11

メソッドチェーンがつかないのではなく、findで見つかった要素に対してメソッドチェーンが効いてしまうのです。 あくまで、divのなかに入っている、p.ttlと、p.numにたいしてテキストを挿入ですが、 一回目の.findした時点で、 p.ttlが、$htmlに入ってるので、 p.ttlには当然p.numが入っていないので、失敗します。 その点回答に追記しますので、お待ち下さい。
miyabi_takatsuk

2019/12/13 04:30

なぜ、うまくいかないのか、コードで、改善案とともに追記しました
risu

2019/12/13 04:36

>ただ、それすると、keisoku_2とやってること大した変わらなくなる なるほど、メモリ上でのことではなくなってしまう感じですね。 >メソッドチェーンがつかないのではなく 2回目のfindは、1回目のfindのものを拾ってしまうということですね。 >その点回答に追記しますので、お待ち下さい。 どうもありがとうございます。お待ちしております。 ひとまず速度について圧倒的に早くなる方法を教えて頂きましたのでベストアンサーにさせて頂きます。ご回答誠にありがとうございました。
risu

2019/12/13 04:38

>なぜ、うまくいかないのか、コードで、改善案とともに追記しました これは大変ご丁寧に・・痛み入ります。変数に再代入に罠があったとはわかりませんでした。本旨と逸れた質問についてまで手取り足取りと色々すみません。本当に、どうもありがとうございました。大変勉強になりました。
risu

2019/12/13 08:32 編集

失礼致します。ふと思い立った方法がございます。 質問の「keisoku_1();」ではテキストをパースする処理が重いとのことで、ならば、テキストのまま扱ってみてはどうかと思いました。つまり「.replace()」です。 https://jsfiddle.net/hjqdxeLg/ 上記リンクですが「keisoku_1();」は質問と変わりありません。変えたのは「get_outline_html()」 で置換すべき場所に「r<!--replace_●●-->」などを書いたことと、「update_outline_html()」でその場所を「.replace()」で置換したことです。 このような処理はどう思われますでしょうか?わかりにくいですかね。
miyabi_takatsuk

2019/12/13 10:32 編集

わかりにくいというよりは、メンテナンス性が落ちるかと思います。 なんにせよ、テキストですと、 要素を変えたい、という時に(例えばdiv → section)定義した静的テキスト、置換の対象文字列などを、全て検索置換しなくてはなりません。 createElementなどを使う利点は、 一箇所修正しただけで、他は修正せずに済むという点です。 よって、わかりやすさは、コメントアウトで補うといいでしょう。 速さに関しては、 レンダリング回数を減らした処理の仕方(私の回答が一例)で、だいぶ遜色ないスピードは出るものと思います。 そして、これ以上は本質問要件から外れていくので、 どうしてもわからない部分は別途質問を立ててください。
risu

2019/12/13 10:40

たしかにヘタな工夫の前にコメントアウトで書いた方がいいですね。 本質問要件はこれにて解決でございます。長々とコメントしてしまいご迷惑をおかけしました。またお目にかかる機会がございましたらその節はどうぞ宜しくお願い申し上げます。それでは、失礼致します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問