.png?q=75&fm=webp)
JavaScriptの速度改善方法!高速化テクニックを公開
JavaScriptが重くてサイトが遅い!そんな悩みを抱えていませんか?
正しい知識と手順さえ知っていれば、誰でも大幅な改善が可能です。
本記事では、async/deferの使い分け、DOM操作の最適化まで、速度改善、高速化テクニックを徹底解説します。
JavaScriptがページ速度に与える影響
JavaScriptはページ速度に3つの重大な影響を与えます。
1つはレンダリングブロッキングによる影響です。
ブラウザはJavaScriptを発見するとHTMLの解析を停止します。特に<head>内のスクリプトはページ初期表示を大幅に遅延させます。
2つ目はメインスレッドの占有による影響も大きいです。
JavaScript実行中、ブラウザは他の処理ができません。重い計算や大量のDOM操作はページをフリーズさせます。
3つ目にファイルサイズの負荷も影響があります。
大きなファイルは、ダウンロード→パース→コンパイル→実行の各段階で時間を消費。モバイルでは特に影響が大きくなります。
JavaScriptの速度改善が重要な理由
JavaScript速度改善が重要なポイントは、ビジネス成果への影響です。
サイトの表示速度が遅くなると、コンバージョン率が低下し離脱率も増加します。
これは、売り上げにも直結する要素でビジネス面で大きな影響を与えます。
次に挙げられる要素として、SEOへの評価もあります。
GoogleのCore Web Vitalsで速度指標が検索順位に影響し、遅いサイトは上位表示されにくくなります。
新規流入や検索からのアクセスが下がることになり、こちらもビジネス面で大きな影響を与えるでしょう。
JavaScriptはブラウザのメインスレッドをブロックし、レンダリングを妨げます。
最適化しないと、HTMLやCSSの処理も遅延し、ページ全体のパフォーマンスが低下します。
JavaScriptの速度改善方法
JavaScript速度改善は3段階で進めます。
第1段階では、async/defer属性の追加、未使用コードの削除、ファイル圧縮を実施。
第2段階では、DOM操作のバッチ化、Promise.allによる並列処理、イベント最適化に取り組みます。
第3段階では、Web Workers、Code Splitting、Resource Hintsを活用。
各段階でPageSpeed Insightsなどのツールで効果を測定しながら進めることで、確実なスコア改善が可能です。
async/defer属性で最適化をかける
JavaScript最適化のおすすめの方法として、async/defer属性で最適化をかける方法があります。
それぞれ解説していきます。
async属性を使用した非同期読み込み
asyncキーワードを使用すると、JavaScriptファイルの読み込みが非同期で行われます。
これにより、HTMLの解析とJavaScriptの読み込みが並行して実行され、ページの表示速度が向上します。
<script src="example.js" async></script>特にファーストビューなどすぐに読み込みが必要な場合に使用されることが多いです。
ただし、注意点があります。
asyncで読み込まれたスクリプトは、読み込みが完了したタイミングで即座に実行されるため、複数のスクリプト間で依存関係がある場合、実行順序が保証されません。
deferキーワードを使用した遅延実行
deferキーワードは、asyncと同様に非同期読み込みを行いますが、スクリプトの実行タイミングがHTMLの解析完了後(DOMContentLoadedイベント発生前)に遅延されます。
<script src="jquery.js" defer></script>
<script src="main.js" defer></script>この場合、複数のスクリプトがある場合でも、HTML内での記述順序で実行されるため、依存関係のあるライブラリを使用している場合も安全です。
未使用JavaScriptの削除
未使用JavaScriptとは、ページで実際に実行されていないコードです。
ライブラリ全体を読み込んでいるが一部しか使っていない、過去に使用していたが現在は不要、デバッグ用コードが残っているなどのケースが該当します。
未使用コードはファイルサイズを増大させ、ダウンロード、パース、コンパイルの時間を無駄に消費します。
削除することでファイルサイズが削減され、表示速度が大幅に改善します。
Chrome DevToolsで未使用コードを特定し。削除を進めましょう。
JavaScriptファイル圧縮を実施
ファイル圧縮(Minify)とは、JavaScriptコードから空白、改行、コメント、不要な文字を削除し、変数名を短縮することでファイルサイズを削減する手法です。
圧縮により、ファイルサイズが削減されます。
以下の方法で、ファイルサイズを大幅に削減できます。
Minification(圧縮)
不要な空白や改行、コメントを削除し、変数名を短縮することで、ファイルサイズを30-50%削減できます。
Gzip圧縮
サーバーレベルでの圧縮により、転送時のファイルサイズを60-80%削減できます。
# Apache設定例
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE text/javascript
</IfModule>
# Nginx設定例
gzip on;
gzip_types application/javascript text/javascript;Tree Shaking
使用されていないコードを自動的に削除する技術で、WebpackやRollupなどのビルドツールで実装できます。
例えば100KBのファイルが50KBになれば、ダウンロード時間が半分になり、特にモバイル環境で効果が顕著です。
パース時間も短縮され、ページ表示速度が大幅に改善します。
実装にはWebpackやViteなどのビルドツールを使用し、本番環境用のビルド時に自動圧縮する設定が一般的です。
圧縮後も機能は完全に保たれ、開発時は読みやすい元のコードで作業できます。
PageSpeed Insightsでも推奨される基本的な最適化施策です。
DOM操作のバッチ化
DOM操作のバッチ化とは、複数のDOM操作をまとめて一度に実行する最適化手法です。
ブラウザはDOM操作ごとにレイアウト計算と再描画を行うため、操作が多いほど処理時間が増大します。
例えば、1000個の要素を1つずつ追加すると1000回の再描画が発生しますが、DocumentFragmentを使いまとめて追加すれば1回で済み、処理速度が数十倍に改善します。
// 悪い例:複数回DOM操作
for (let i = 0; i < 1000; i++) {
element.innerHTML += '<div>item</div>';
}
// 良い例:1回で処理
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = 'item';
fragment.appendChild(div);
}
element.appendChild(fragment);
実装はDocumentFragmentに要素を追加し、最後に一度だけDOMに挿入します。
リスト表示やテーブル生成など大量要素を扱う場合に特に効果的で、体感速度が劇的に向上します。
Promise.allによる並列処理
romise.allは、複数の非同期処理を並列実行し、全ての処理が完了するまで待機する関数です。
通常のawaitで順次実行すると、各処理の待ち時間が累積しますが、Promise.allを使えば同時に実行されるため、総処理時間が大幅に短縮されます。
// 順次実行(遅い)
const res1 = await fetch('/api/1');
const res2 = await fetch('/api/2');
// 並列実行(速い)
const [res1, res2] = await Promise.all([
fetch('/api/1'),
fetch('/api/2')
]);例えば、3つのAPI呼び出しをそれぞれ1秒かかるとします。
順次実行では合計3秒必要ですが、Promise.allなら並列実行されるため約1秒で完了します。
ただし、1つでも失敗すると全体が失敗扱いになるため、エラーハンドリングが重要です。
個別の成功・失敗を判定したい場合はPromise.allSettledを使用します。
互いに依存しない複数のAPI呼び出しやデータ取得で効果絶大です。
イベント最適化
イベント最適化とは、イベントリスナーの登録方法や実行頻度を改善する手法です。主に3つの方法があります。
イベントデリゲーションは、多数の要素に個別リスナーを登録せず、親要素1つにまとめることでメモリ消費とDOM操作を削減します。
// (悪い例:個別にリスナーを登録)1000個の要素にそれぞれリスナーを登録
const items = document.querySelectorAll('.list-item');
items.forEach(item => {
item.addEventListener('click', function(e) {
console.log('クリックされた:', this.textContent);
this.classList.toggle('active');
});
});
// 問題点:
// - 1000個のイベントリスナーを登録(メモリ消費大)
// - 動的に追加された要素にはイベントが効かない
// - パフォーマンスが低下
// (良い例:イベントデリゲーション)親要素に1つだけリスナーを登録
const list = document.querySelector('.list');
list.addEventListener('click', function(e) {
// クリックされた要素が.list-itemか確認
const item = e.target.closest('.list-item');
if (item) {
console.log('クリックされた:', item.textContent);
item.classList.toggle('active');
}
});
// メリット:
// - リスナーは1つだけ(メモリ効率的)
// - 動的に追加された要素も自動的に対応
// - パフォーマンスが大幅に向上
デバウンス・スロットルは、スクロールやリサイズなど高頻度イベントの実行回数を制限し、処理負荷を軽減します。
Passive Event Listenerは、addEventListener のpassiveオプションでブラウザのスクロール処理をブロックせず、スムーズな操作を実現します。
これらの最適化により、大量要素を扱うリストやスクロール処理で劇的な改善効果が得られます。
Web Workersを活用
Web Workersは、JavaScriptを別スレッドで実行し、メインスレッドをブロックせずに重い処理を実行する技術です。
通常のJavaScriptはシングルスレッドのため、重い計算中はUIがフリーズしますが、Workersを使えば解決できます。
画像処理、大量データ計算、複雑なソート処理などをWorkerに任せることで、メインスレッドはUI操作に専念でき、処理中もスムーズな操作が可能になります。
// image-worker.js
self.addEventListener('message', function(e) {
const imageData = e.data;
const pixels = imageData.data;
// グレースケール変換
for (let i = 0; i < pixels.length; i += 4) {
const gray = pixels[i] * 0.299 +
pixels[i + 1] * 0.587 +
pixels[i + 2] * 0.114;
pixels[i] = pixels[i + 1] = pixels[i + 2] = gray;
}
// 結果を返す
self.postMessage(imageData, [imageData.data.buffer]);
// Transferable Objects使用でコピーコスト削減
});
// main.js
const imageWorker = new Worker('image-worker.js');
imageWorker.addEventListener('message', function(e) {
const processed = e.data;
ctx.putImageData(processed, 0, 0);
console.log('処理完了');
});
button.addEventListener('click', function() {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
imageWorker.postMessage(imageData, [imageData.data.buffer]);
// UIはブロックされず、他の操作が可能
console.log('処理中でもこのログが即座に表示される');
});ただしWorkerからはDOMアクセスができず、計算処理専用です。
データはpostMessageで送受信し、大容量データにはTransferable Objectsを使用します。
Code Splittingを活用
Code Splittingは、JavaScriptを複数ファイルに分割し、必要な時だけ読み込む最適化手法です。
全コードを最初に読み込まず、初期表示に必要な部分だけ先に読み込むことで、表示速度が大幅に向上します。
Code Splitting活用前のコード例
// 全てを1つのバンドルに含める
import Home from './pages/Home';
import About from './pages/About';
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';
import HeavyChart from './components/HeavyChart';
import VideoPlayer from './components/VideoPlayer';
// 結果:bundle.js(500KB)
// 問題:ユーザーがHomeページしか見なくても全て読み込む下記のようにファイルを分割しておき、すぐ読み込みが必要なものと、そうでないものを分けます。
Code Splitting活用のコード例
// 必要な時だけ読み込む
// 初期バンドル:home.js(50KB)のみ
// 他のページは遷移時に読み込む
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
// 結果:初期読み込みが90%削減例えば100KBのアプリを初期用30KBと機能別70KBに分割すれば、初期読み込み時間が70%短縮されます。
SPAや大規模アプリで特に効果的で、LCPスコアが劇的に改善します。
実装にはDynamic Import、React.lazy、Webpackの分割機能を使用します。
ルート単位やコンポーネント単位で分割することで、初期バンドルサイズを削減し、PageSpeed Insightsのスコアを大幅に改善できます。
Resource Hintsを活用
Resource Hintsは、ブラウザに次に必要なリソースを事前通知し、先読みや事前接続を行う最適化手法で主に4種類あります。
dns-prefetchはDNS解決のみ先実行する軽量版です。
外部ドメインのDNS解決を事前に実行し、実際のリクエスト時の待ち時間を削減します。
dns-prefetchソース例
<!-- 外部ドメインのDNS解決を先行 -->
<head>
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head>preconnectは外部ドメインへの接続を事前確立し、DNS解決とTLS接続時間を短縮します。
DNS解決、TCP接続、TLS/SSLハンドシェイクを事前に完了させます。dns-prefetchより包括的で効果が高いですが、コストも高くなります。
preconnectソース例
<head>
<!-- 重要な外部ドメインへの完全な事前接続 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://api.example.com">
</head>preloadは重要リソースを優先読み込みし、LCP改善に効果的です。
preloadソース例
<head>
<!-- JavaScript -->
<link rel="preload" href="/js/critical.js" as="script">
<!-- CSS -->
<link rel="preload" href="/css/main.css" as="style">
<!-- フォント -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<!-- 画像 -->
<link rel="preload" href="/images/hero.jpg" as="image">
<!-- 動画 -->
<link rel="preload" href="/videos/intro.mp4" as="video">
</head>prefetchは次ページで必要なリソースをアイドル時に先読みし、ページ遷移を高速化します。
適切な使用で外部API応答が200〜500ms短縮され、ページ遷移が体感的に瞬時になります。CDNや外部サービス利用サイトで特に効果的です。
JavaScriptの速度改善方法まとめ
JavaScript速度改善は、測定から始まり継続的な最適化が必要です。
本記事で紹介した手法は、いずれも実証済みの効果的な施策ですが、全てを一度に実装する必要はありません。
まずPageSpeed Insightsで現状を測定し、サイトの特性とユーザー行動に合わせて優先順位をつけましょう。
初級の基本施策だけでも、大きな改善が期待できます。
速度改善の本質は、ユーザーに快適な体験を提供することです。1秒の短縮が、離脱率の低下とコンバージョン率の向上につながります。
完璧を目指すのではなく、継続的な改善を心がけてください。小さな一歩が大きな成果を生み出します。
