139
76

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

リンクアンドモチベーション Advent Calendar2021Advent Calendar 2021

Day 23

ブラウザ JavaScript / Node.js の仕組みを知ろう! ~トラブルに迅速に立ち向かえる様に

Last updated at Posted at 2021-12-19

はじめに

近年 JavaScript の需要は増し、Web Application のほとんどは JavaScript を使って動いているのではないかと思います。

もともと JavaScript は DOM API (Document Object Model、HTML を JavaScript でから操作できる) の為にありました。DOM API を更に使いやすくした jQuery 等が流行りましたね。

しかし JavaScript の活用範囲は拡大し、現在では以下の様々な用途で利用されます。

  • フロントエンド JS 開発の環境 (Babel, Webpack)
  • Web バックエンド ランタイムサーバー (Express)
  • クロスプラットフォームなデスクトップアプリケーションランタイム (Electron)
  • AWS Lambda 等のサーバーレス環境のランタイム

例えばフロントエンド開発をするとき、ローカルマシンに Node.js をインストールして使いますが、Node.js では使えるがブラウザ JavaScript では使えない API (ファイル入出力等) があります。同じ JavaScript の筈なのに何故でしょう?

つまり、どこまでが JavaScript なのでしょうか?

巷でよく聞く ECMAScript (≒ JavaScript ?) とは何なのかを追っていきましょう。

Web ブラウザの仕組み (標準仕様と実装)

まずは JavaScript 実行のベースとなっている Web ブラウザの仕組みについて追っていきましょう。

構成要素

Web ブラウザの仕組みを図に書き出してみると、おおよそ以下の様になります。

以下は Goole Chrome での例です。
他ブラウザでも基本的なアーキテクチャはほぼ同じですが、一部異なる箇所はあります。

ブラウザ JavaScript _ Node.js の仕組みを知ろう!.drawio.png

以下は図中の構成要素の説明です。

# 構成要素 実装系 説明
1 ユーザーインターフェース Google Chrome ウインドウやタブ、URL バー等、デスクトップアプリケーションとしての UI 部分です。 (つまり OS 毎に実装が異なります)
2 ブラウザ エンジン Google Chrome UI 部分 (OS 上のプロセス) と、レンダリングエンジン間の橋渡しをします。
3 レンダリング エンジン Blink 主に HTML の画面描画をしますが、JavaScript エンジンの初期化・起動等、様々な処理をします。
4 JavaScript エンジン V8 ECMAScript (ECMA-262) 仕様に準拠したエンジンで、JavaScript コンテキスト (実行空間) の生成と、ユーザー JavaScript コードの実行をします。

(1) ユーザーインターフェース

ウインドウやメニュー等、アプリケーション (プロセス) としての UI 部分です。
この部分の実装は OS によって異なります。

Screen Shot 2021-12-19 at 17.01.18.png

Screen Shot 2021-12-19 at 16.43.06.png

(2) ブラウザ エンジン

ブラウザエンジンは、UI 部分 (メインプロセス) と、レンダラプロセスとの橋渡しを行います。

ブラウザ JavaScript _ Node.js の仕組みを知ろう!-ブラウザ-レンダラ.drawio.png

Google Chrome では、ウィンドウやタブを開く度にレンダリング処理をするサブプロセスが立ち上がります。

Screen Shot 2021-12-19 at 16.53.37.png

Chronium 系ブラウザではマルチプロセス動作をしますが、他ブラウザはそうではありません。

(3) レンダリング エンジン

Google Chrome では、レンダリングエンジンに Blink を採用しています。

Blink は、Safari 等で使われている WebKit の派生プロジェクトです。

レンダリングエンジンは主に HTML/CSS の描画処理をします。 (本書では描画については述べません)
後述する「JavaScript エンジン」への実行指示もレンダリングエンジンが行います。

(4) JavaScript エンジン

※次章で詳しく説明をします。

JavaScript コンテキストと Web API

ブラウザのウインドウやタブでページ遷移 (URL の変更) が発生すると、レンダリングエンジンから JavaScript エンジンへ 新しい JavaScript コンテキストの生成 が指示されます。

HTML 中に含まれる <script> タグで入力したユーザー JavaScript は、この生成されたコンテキスト上に取り込まれ実行されます。

図解

ブラウザ JavaScript _ Node.js の仕組みを知ろう!-JS と Web API.drawio.png

# 処理 説明
1 JavaScript エンジン (V8) は、新しいコンテキスト (JS 実行空間) を生成します JavaScript の標準的な構文、オブジェクトが利用可能になります
2 Web IDL で定義された Web API がコンテキスト内に JavaScript オブジェクトとして定義される レンダリングエンジンが提供する Web API が利用可能になります
3 ユーザー JavaScript コードを、コンテキスト内で実行する

V8 は ECMASCRIPT に準拠

ここで最も重要な事は JavaScript エンジン (V8) は ECMASCRIPT (ECMA-262) 仕様の実装系である という事です。

例えば、ブラウザ JavaScript で良く使われる setTimeout()console.log() は、実は ECMASCRIPT 仕様ではありません。
これらの API 実装はブラウザが提供しており、 Web API 仕様に当たります。

JS 仕様 JavaScript 構文/API
JavaScript (ECMASCRIPT) 標準組込みオブジェクト Array, Number, Promise, JSON
式と演算子 +, -, this, function
文と宣言 if...else, for...in
Web API Console API console.log()
Document Document.getElementById()

↑ に挙げたのは一例で、網羅はしてません。

API がどの仕様に準拠しているか?

MDN のパンくずリストを見ると、その API が何の仕様に基づいているか分かります。
例えば Promise は JavaScript (ECMAScript) 仕様です。

Screen Shot 2021-12-19 at 17.59.57.png

関数 setTimeout()Web API 仕様であることが分かります。

Screen Shot 2021-12-19 at 18.00.10.png

他ブラウザでは?

前章では Google Chrome を例に説明をしましたが、他ブラウザでも基本的な仕組みは同じで、実装系 (BlinkV8 等) が以下の通り異なります。

ブラウザ レンダリングエンジン JavaScript エンジン 補足説明
Google Chrome Blink V8 Chromium ベース
Mozilla FireFox Gecko SpiderMonkey Mozilla がメインでメンテ
Safari WebKit JavaScriptCore Apple がメインでメンテ
Microsoft Edge Blink V8 Chromium ベースに移行
Microsoft Edge ※旧版 EdgeHTML Chakra 2021年3月9日にサポートを終了
Internet Explorer Trident Chakra 2022年6月16日にサポートを終了

モバイルもありますが、割愛。

Node.js の仕組み (標準仕様と実装)

ここまでで Web ブラウザの仕組みを追ってみました。
では Node.js はどういう仕組になっているのでしょうか?

Web ブラウザの仕組みを知っている事が Node.js の理解にも繋がります。

図解

ブラウザ JavaScript _ Node.js の仕組みを知ろう!-Node.js.drawio.png

# 処理 説明
1 JavaScript エンジン (V8) は、新しいコンテキスト (JS 実行空間) を生成します JavaScript の標準的な構文、オブジェクトが利用可能になります
2 Node.js 独自のコアモジュール (Node.js API) をコンテキスト中に定義する コアモジュール (JavaScript) コードは Node.js リポジトリの lib/ 配下にあります。

実際の API 処理の実態は Node.js の C++ コード側にあり、lib/ 配下の JavaScript コードは該当する C++ 実装の Binding を取得してコード呼び出しをしています。
3 ユーザー JavaScript コードを、コンテキスト内で実行する

Node.js も Chrome と同じ V8 エンジン

Node.js は JavaScript エンジンに V8 を採用しています。従って標準的な JavaScript 仕様 (ECMASCRIPT) の範囲では Google Chrome と同じ様に動作します。

しかし Web ブラウザの章で述べた通り setTimeout()console.log() の API は ECMASCRIPT 仕様に含まれていませんが、 Node.js ではこれらの API を問題なく使えます。何故でしょうか?

その答えは Node.js 独自の Node.js API (コアモジュール) として、これらの API が実装されているからです。

つまり上記の API は Web ブラウザでの Web API 仕様との互換性を保ちために追加されたものですが、 あくまで Node.js 独自の API であり、Web API 仕様を準拠しているわけではありません。

setTimeout() を使用して、 指定したミリ秒後に コードの実行をスケジュールすることができます。 この関数はブラウザの JavaScript API の window.setTimeout()に似ていますが、 コードの文字列を渡して実行することはできません。

アップデートによる影響

Node.js をアップデートする際は前述の構造から次の2点が影響範囲であると分かります。

  1. JavaScript エンジン V8 の変更
  2. Node.js (コアモジュール) の変更

ECMAScript 仕様は後方互換性を最大限尊重します。
従って、JavaScript エンジン (V8) の変更による既存のコードへの影響は比較的少ないと言えます。

例えば Chrome 等の Web ブラウザはバックグラウンドで秘密裏にアップデートされています。

一方で、Node.js (コアモジュール) の変更では、破壊的変更が加えられる事は珍しくありません。

実際に Node.js のコードを追ってみよう!

ここからはかなりニッチな内容になるので興味ある方だけ ^^;

ユーザー JavaScript コードから fs.chmod() を呼んだ場合に、この API の実態 (C++ 実装) は Node.js ソースコード の何処にあるのか?を追います。

お題 → fs.chmod()

Node.js 公式の API Documentation に fs.chmod() の記載があります。

これをお題にコードを追いましょう。

import { chmod } from 'fs';

chmod('my_file.txt', 0o775, (err) => {
  if (err) throw err;
  console.log('Node.js 公式の chmod 使用例です.');
});

fs モジュールのフロント実装.

ユーザースクリプトで import { chmod } from 'fs'; しているモジュールの実態は lib/fs.js にあります。

この function の JS 実装はたった6行しか無く、最終行 L1315binding.chmod() が C++ 側の実装を呼んでいる箇所になります。

function chmod(path, mode, callback) {
  path = getValidatedPath(path);
  mode = parseFileMode(mode, 'mode');
  callback = makeCallback(callback);

  const req = new FSReqCallback();
  req.oncomplete = callback;
  binding.chmod(pathModule.toNamespacedPath(path), mode, req);  // ← ここがキモ
}

この binding オブジェクトはファイルの先頭 L59 で生成されています。

const binding = internalBinding('fs');

JavaScript → C++ 実装への Binding の仕組み.

Node.js の JavaScript / C++ Binding の仕組み自体は README.md に書いてあります。

書いてある事は難しいが、要は ↓ という事です。

C++ 側で NODE_MODULE_CONTEXT_AWARE_INTERNAL(モジュール名, Initialize関数) 定義したモジュールは、JavaScript 側で internalBinding('モジュール名') で取得できるよ

実際に lib/fs.js JavaScript モジュールに該当する C++ 実装は src/node_file.cc ファイルになります。

C++ src/node_file.cc ファイルの最終行 L2535 にバインディングの定義があります。

NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs, node::fs::Initialize)

JavaScript fs.chmod() に該当する C++ 実装は、このコード中の以下の関数 L2114 です。

/* fs.chmod(path, mode);
 * Wrapper for chmod(1) / EIO_CHMOD
 */
static void Chmod(const FunctionCallbackInfo<Value>& args) {
  // 省略...
}

JavaScript API 名 chmod とこの C++ 関数 Chmod との紐付けは同ファイルの Initialize() 関数内 L2439 にあります。

  env->SetMethod(target, "chmod", Chmod);
  env->SetMethod(target, "fchmod", FChmod);

  env->SetMethod(target, "chown", Chown);
  env->SetMethod(target, "fchown", FChown);
  env->SetMethod(target, "lchown", LChown);
139
76
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
139
76

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?