Javascript (EcmaScript と言った方が良い) では、ファイルの読み取りと書き込みの関数は指定しないことを知る必要があります。
実際、Javascript は多くの環境 (ブラウザ、または NodeJS は環境の例です) で使用される言語に過ぎず、作業するためのオブジェクトや関数を提供します。
Node は、require()
という特殊機能を使用してコードをモジュールで整理する方法を提供した最初の環境です。 それはどのように機能するのでしょうか。 ゼロから実装してみましょう。
以下はrequire
が機能している例です。
//test.jsmodule.exports = "Hello World";
//main.jsconst test = require("./test.js"); console.log(test)
そのrequire
関数を書いてみましょう。
require() 関数は何をすべきか
require
関数に期待されること:
- javascript ファイルの内容を文字列で読み取る
- そのコードを評価する
- エクスポートした関数/オブジェクトをキャッシュに保存して後で使用する(ファイルを 1 回だけ読み取る)
Disclaimer
1 つの投稿で全体の NodeJS を再構築することはありません。 実際、私は多くの NodeJS チェックとギグルスを実装しません、私たちは物事がどのように動作するかを理解することにのみ興味があります。
私たちはまだ、fs
モジュールをロードするために本物の require
関数が必要になります。 この投稿は遅かれ早かれ終わらなければならないのです。
myRequire() function
これがそのコードです:
//file setup.jsconst fs = require('fs');myRequire.cache = Object.create(null); //(1)function myRequire(name) { if (!(name in myRequire.cache)) { let code = fs.readFileSync(name, 'utf8'); //(2) let module = {exports: {}}; //(3) myRequire.cache = module; //(4) let wrapper = Function("require, exports, module", code); //(5) wrapper(myRequire, module.exports, module); //(6) } return myRequire.cache.exports; //(7)}...
myRequire 変数を宣言し忘れていませんか? Javascript では、function
キーワードで宣言された関数は他のコードより前に評価される (関数は「hoisted」) ため、宣言前でも参照できます。
また、関数はプロパティを持つことができるので (これは javascript です !)、myRequire
関数に cache
プロパティを追加できます (ステップ 1)。 この関数では、オブジェクトのプロトタイプを指定することができますが、ここではプロトタイプを指定しないことにしました。 なぜでしょうか。 この方法では、ランタイムによって宣言された他の関数やプロパティを混乱させることはありません。 以下はその説明です。
myRequire
に戻りましょう。 インポートするファイルがキャッシュにない場合、ディスクからファイルを読み込みます (ステップ 2)。
次に、プロパティ exports
を 1 つだけ持つ空の module
オブジェクトを宣言します (ステップ 3)。
私たちはこの空のモジュールをキャッシュに追加し、キーとしてファイル名を使用し、そして魔法が起こる (ステップ 4)。 最初の方法は、eval()
関数を使用する方法ですが、これは少し危険なので (スコープを混乱させる)、使用するのは非常にお勧めできません。
文字列にあるコードを評価する 2 番目の方法は、Function
コンストラクタを使用する方法です。 このコンストラクタは、引数で文字列を、コードで文字列を受け取ります。
したがって、基本的には、これらの変数で新しい関数を作成します (ステップ 5)。 require
, exports
, そして module
です。 この記事の最初の例、ファイル test.js
で少し考えてみましょう: それは
function(require, exports, module) { module.exports = "Hello World" }
となり、2番目のファイル main.js
:
function(require, exports, module) { const test = require("./test.js"); console.log(test) }
ファイル内で「グローバル」と思われていた変数が、実際に関数の引数として渡されます。
最後のステップ: 関数の実行
我々は、関数を持っていますがその関数は決して実行されない wrapper
変数を作成してしまいました(ステップ 6)。
wrapper(myRequire, module.exports, module);
2 番目の変数 (exports
であるべき) が module.exports
への単なるハンドルであることに注意してください; NodeJS の作成者は、これがより少ないコードを書くのに役立つと考えました…
Node が機能を実行すると、「エクスポートした」すべて(あなたのパブリック API)がキャッシュにリンクされます。 コンパイラーによって最初に発見されたとき、それはダミーの { exports: {} }
オブジェクトを指していましたが、今ではあなたのモジュールを含んでいます。 ラッパー関数に myRequire
を渡しているので、これからテストファイルで require
を使うことができますが、私たちの require が呼び出されます。 もし私を信用しないなら console.log を追加してください 😉
最後に… myRequire
は、あなたが宣言した export
したもの (ステップ 7) と、このコードを再度評価する必要がないように、キャッシュに保存しておいたものを返します。
Final considerations
このコードの例は、何が起こっているかを説明するいくつかのコンソール ログとともに、ここで見ることができます。 本 (Eloquent Javascript) は素晴らしいのですが、もっと理解したいという衝動に駆られ、頭だけでは理解できないことをデバッガで試してみました。
javascriptをもっと理解したいなら、ぜひ本を読んでみてください。
タグ:Javascript – Nodejs