JavaScriptCoreとは?AppleのJavaScriptエンジンの仕組みと特徴を解説

「JavaScriptCoreって何?」「SafariではどうやってJavaScriptが動いているの?」「V8エンジンとの違いは?」

こんな疑問を持ったことはありませんか。
JavaScriptCoreは、SafariブラウザやiOSアプリでJavaScriptコードを実行するためのエンジンです。

この記事では、JavaScriptCoreの基本的な概念から内部の仕組み、実際の使い方まで詳しく解説します。
プログラミング初心者の方でも理解できるように、専門用語も丁寧に説明していきます。

スポンサーリンク

JavaScriptCoreとは

JavaScriptCoreは、Appleが開発したJavaScriptエンジン(JavaScript実行環境)です。
NitroやSquirrelFishという別名でも知られています。

JavaScriptエンジンとは

JavaScriptエンジンとは、JavaScriptのコードを解析して実行するためのプログラムです。
人間が書いたJavaScriptのコード(テキスト)を、コンピュータが理解できる命令に変換して実行します。

例えば、Webページで「ボタンをクリックしたらポップアップを表示する」といった動作は、JavaScriptエンジンが裏で動いているおかげで実現できています。

JavaScriptCoreの位置づけ

JavaScriptCoreは、WebKitというWebブラウザエンジンの一部として開発されています。
WebKitは、Webページを画面に表示するための仕組み全体を指し、その中でJavaScript部分を担当しているのがJavaScriptCoreです。

主な使用例:

Safari(macOSとiOSのWebブラウザ)で使われています。
Mail(メールアプリ)やApp Store(アプリストア)などのApple製アプリでも使われています。
iOS/macOSアプリの開発者が、アプリ内でJavaScriptを実行するために使えます。

他のJavaScriptエンジンとの違い

代表的なJavaScriptエンジンには、以下のようなものがあります。

V8: GoogleのChromeブラウザとNode.jsで使われているエンジンです。
SpiderMonkey: Mozillaが開発し、Firefoxで使われているエンジンです。
Chakra: Microsoftが開発し、以前のEdgeブラウザで使われていたエンジンです(現在のEdgeはV8を使用)。

それぞれのエンジンは、JavaScriptを実行するという同じ目的を持ちながらも、内部の仕組みや最適化の方法が異なります。

JavaScriptCoreの歴史

JavaScriptCoreは1998年にAppleによって開発されました。
もともとはMac OS Xの一部として作られ、その後2005年にWebKitプロジェクトの一部としてオープンソース化されました。

主な進化の歴史:

1998年: Mac OS Xの一部として開発開始しました。
2005年: WebKitプロジェクトとしてオープンソース化されました。
2008年: SquirrelFish(高速化版)がリリースされました。
2008年: SquirrelFish Extreme(さらなる高速化版)がリリースされました。
2013年: iOS 7.0とOS X 10.9でJavaScriptCore.frameworkが利用可能になりました。

現在では、継続的に改良が加えられ、パフォーマンスと機能が向上し続けています。

JavaScriptCoreの仕組み

JavaScriptCoreがどのようにしてJavaScriptコードを実行しているのか、その内部の仕組みを見ていきましょう。

4層のコンパイルシステム

JavaScriptCoreは、コードの実行速度を最適化するために、4つの層(ティア)を使い分けています。
実行回数が少ないコードは簡単な方法で実行し、何度も実行されるコード(ホットコード)は高度な最適化を行います。

第1層: LLInt(Low Level Interpreter):

JavaScriptコードを最初に実行する際に使われます。
起動速度を重視し、すぐに実行を開始できます。
バイトコード(中間コード)をそのまま実行します。

第2層: Baseline JIT:

関数が一定回数(約60回)実行されると、この層に移行します。
JIT(Just-In-Time)コンパイラが、バイトコードを機械語(コンピュータが直接実行できるコード)に変換します。
LLIntよりも高速に実行できます。

第3層: DFG JIT(Data Flow Graph JIT):

関数がさらに多く実行される(約1000回のループなど)と、この層に移行します。
データフローグラフを使って、より高度な最適化を行います。
型情報を活用して、不要なチェックを省略できます。

第4層: FTL JIT(Faster Than Light JIT):

非常に頻繁に実行される関数(数千回〜数万回)に対して使われます。
LLVM(最適化コンパイラ基盤)を使って、最高レベルの最適化を行います。
最も高速に実行できますが、最適化に時間がかかります。

コード実行の流れ

JavaScriptコードが実行されるまでの流れを、順を追って説明します。

1. 字句解析(Lexer):

JavaScriptのソースコード(テキスト)を読み込みます。
コードをトークン(単語のようなもの)に分解します。
例: let x = 10;let, x, =, 10, ;という5つのトークンに分解します。

2. 構文解析(Parser):

トークンの並びから、構文木(Abstract Syntax Tree: AST)を作ります。
構文木は、コードの構造を木のような形で表現したものです。
文法エラーがあれば、この段階で検出されます。

3. バイトコード生成:

構文木から、バイトコードという中間コードを生成します。
バイトコードは、機械語よりは人間に読みやすく、ソースコードよりは実行しやすい形式です。

4. 実行と最適化:

最初はLLIntでバイトコードを実行します。
実行回数に応じて、Baseline JIT → DFG JIT → FTL JITと段階的に最適化されます。
プロファイリング情報(型情報や実行頻度など)を収集し、それを元に最適化を行います。

型推論と最適化

JavaScriptは動的型付け言語なので、変数の型は実行時まで決まりません。
しかし、実際には同じ変数はほぼ同じ型で使われることが多いです。

JavaScriptCoreは、この特性を利用して最適化を行います。

プロファイリング:

LLIntとBaseline JITは、コードの実行中に型情報を収集します。
「この変数は常に数値として使われている」といった情報を記録します。

投機的最適化:

DFG JITとFTL JITは、収集された型情報を元に「この変数は数値だろう」と推測します。
その推測に基づいて、型チェックを省略した高速なコードを生成します。

デオプティマイゼーション(最適化解除):

もし推測が外れた場合(数値だと思っていた変数に文字列が入ってきた場合など)、最適化されたコードから元のコードに戻ります。
これをOSR Exit(On-Stack Replacement Exit)と呼びます。

メモリ管理

JavaScriptCoreは、ガベージコレクション(自動メモリ管理)を行います。

マークアンドスイープ方式:

使用中のオブジェクトに印(マーク)をつけます。
マークされていないオブジェクト(もう使われていないオブジェクト)をメモリから削除します。

世代別ガベージコレクション:

新しく作られたオブジェクトと、長く生き残っているオブジェクトを分けて管理します。
新しいオブジェクトは頻繁にチェックし、古いオブジェクトは時々チェックすることで、効率を上げます。

JavaScriptCoreの特徴

JavaScriptCoreには、他のJavaScriptエンジンと比較して以下のような特徴があります。

起動時間の速さ

JavaScriptCoreは、起動時間(コードの実行を開始するまでの時間)を重視した設計になっています。

LLIntは、構文解析の後すぐに実行を開始できます。
複雑な最適化は、コードが何度も実行された後に行われます。
Webページを開いた瞬間の応答性が良くなります。

メモリ効率

Appleのデバイス(iPhone、iPadなど)はバッテリー駆動なので、メモリ使用量を抑えることが重要です。

JavaScriptCoreは、V8エンジンと比べてメモリ使用量が少ない傾向があります。
バッテリー消費を抑えることができます。

多層JITコンパイル

4つの異なる層を使い分けることで、柔軟な最適化が可能です。

軽いコードは軽い処理で実行できます。
重いコード(頻繁に実行されるコード)だけを徹底的に最適化できます。
全体として、バランスの取れたパフォーマンスを実現できます。

Apple製品との統合

Appleのエコシステムに深く統合されています。

Safariブラウザで最適化されています。
iOS/macOSアプリから簡単に利用できます。
SwiftやObjective-Cとの連携がスムーズです。

V8エンジンとの比較

GoogleのV8エンジンと比較すると、以下のような違いがあります。

設計思想の違い

JavaScriptCore:

起動時間を重視しています。
メモリ効率を重視しています。
段階的な最適化(4層)を行います。

V8:

実行速度(ピークパフォーマンス)を重視しています。
積極的な最適化を行います。
より少ない層(2〜3層)で最適化を行います。

使用場面の違い

JavaScriptCore:

Safariブラウザで使われます。
iOS/macOSアプリで使われます。
Bunという新しいJavaScriptランタイムでも使われています。

V8:

Google Chromeブラウザで使われます。
Node.js(サーバーサイドJavaScript)で使われます。
Electron(デスクトップアプリフレームワーク)で使われます。

パフォーマンスの特性

JavaScriptCore:

起動が速いです。
メモリ使用量が少ないです。
長時間実行されるコードでは、V8とほぼ同等のパフォーマンスを発揮します。

V8:

ピーク時のパフォーマンスが高いです。
サーバーサイドアプリケーションに適しています。
メモリを多く使う傾向があります。

JavaScriptCoreの使い方

開発者がJavaScriptCoreを実際に使う方法を紹介します。

iOS/macOSアプリでの利用

iOS 7.0とOS X 10.9以降では、JavaScriptCore.frameworkを使ってアプリ内でJavaScriptを実行できます。

基本的な使い方(Swift):

import JavaScriptCore

// JavaScriptコンテキストを作成
let context = JSContext()!

// JavaScriptコードを実行
let result = context.evaluateScript("1 + 2 + 3")

// 結果を取得
print(result?.toInt32() ?? 0)  // 6

ネイティブコードとの連携

JavaScriptとSwift/Objective-Cのコードを相互に呼び出すことができます。

Swift関数をJavaScriptから呼び出す:

import JavaScriptCore

let context = JSContext()!

// Swift関数を定義
let doubleFunction: @convention(block) (Int) -> Int = { value in
    return value * 2
}

// JavaScriptコンテキストに登録
context.setObject(doubleFunction, forKeyedSubscript: "double" as NSString)

// JavaScriptから呼び出す
let result = context.evaluateScript("double(5)")
print(result?.toInt32() ?? 0)  // 10

JavaScriptの関数をSwiftから呼び出す:

import JavaScriptCore

let context = JSContext()!

// JavaScript関数を定義
context.evaluateScript("""
    function add(a, b) {
        return a + b;
    }
""")

// JavaScript関数を取得
let addFunction = context.objectForKeyedSubscript("add")

// Swiftから呼び出す
let result = addFunction?.call(withArguments: [5, 3])
print(result?.toInt32() ?? 0)  // 8

エラーハンドリング

JavaScriptコードで例外が発生した場合、エラーハンドラーで処理できます。

import JavaScriptCore

let context = JSContext()!

// エラーハンドラーを設定
context.exceptionHandler = { context, exception in
    if let error = exception {
        print("JavaScriptエラー: \(error)")
    }
}

// 文法エラーのあるコードを実行
context.evaluateScript("**INVALID**")
// 出力: JavaScriptエラー: SyntaxError: Unexpected token '**'

実用的な活用例

アプリ内でカスタムスクリプトを実行:

ユーザーが独自のスクリプトを書いて、アプリの動作をカスタマイズできます。
ビルドせずにコードを更新できるため、アプリストアの審査を待たずに機能追加できます。

データ処理:

複雑なビジネスロジックをJavaScriptで記述し、複数のプラットフォーム(iOS/Android/Web)で共有できます。

動的なUI生成:

JavaScriptでUI構造を記述し、ネイティブコードで描画することで、柔軟なUIを実現できます。

JavaScriptCoreを使う際の注意点

パフォーマンスの考慮

JavaScriptとネイティブコード間の呼び出しには、オーバーヘッド(余分な処理時間)がかかります。
頻繁に呼び出す必要がある処理は、ネイティブコードで実装した方が高速です。

メモリ管理

JavaScriptとネイティブコードの間でオブジェクトを共有する際は、循環参照に注意が必要です。
適切にメモリ管理を行わないと、メモリリークが発生する可能性があります。

スレッドセーフティ

JSContextは、作成されたスレッドでのみ使用できます。
別のスレッドから操作する場合は、適切な同期処理が必要です。

デバッグ

Safariの開発者ツールを使って、JavaScriptコードをデバッグできます。
Xcodeで実行中のアプリに対して、Safariからデバッガーを接続できます。

JavaScriptCoreの今後

JavaScriptCoreは、現在も活発に開発が続けられています。

継続的なパフォーマンス改善

新しい最適化技術が定期的に追加されています。
ECMAScript(JavaScriptの標準仕様)の新しい機能のサポートが追加されています。

WebAssemblyのサポート

WebAssembly(高速に実行できるバイナリ形式のコード)のサポートが強化されています。
C/C++などの言語で書かれたコードを、JavaScriptCoreで実行できます。

クロスプラットフォーム開発への対応

BunなどのJavaScriptランタイムでJavaScriptCoreが採用されるなど、Apple製品以外での利用も広がっています。

まとめ

JavaScriptCoreは、Appleが開発したJavaScriptエンジンで、Safariブラウザを始めとする多くのApple製品で使われています。

主な特徴:

4層のJITコンパイルシステムで段階的に最適化を行います。
起動時間の速さとメモリ効率を重視した設計になっています。
iOS/macOSアプリでJavaScriptを実行できるJavaScriptCore.frameworkを提供しています。
ネイティブコードとJavaScriptコードを相互に呼び出せます。

V8エンジンとの違い:

JavaScriptCoreは起動時間とメモリ効率を重視し、V8は実行速度を重視しています。
JavaScriptCoreはApple製品に最適化され、V8はChrome、Node.js、Electronで使われています。

開発者にとってのメリット:

アプリ内でJavaScriptを実行できるため、動的な機能追加が可能です。
複数のプラットフォームでビジネスロジックを共有できます。
SwiftやObjective-Cとの連携がスムーズです。

JavaScriptCoreを理解することで、Safariがどのように動いているのか、iOS/macOSアプリでどのようにJavaScriptを活用できるのかが分かります。
特にApple製品向けの開発を行う方にとって、JavaScriptCoreは強力なツールとなります。

参考情報

本記事は以下の信頼できる情報源を参照して作成しました。

この記事は2025年2月9日時点の情報に基づいています。

コメント

タイトルとURLをコピーしました