ホームページ > 記事 > ウェブフロントエンド > JavaScript のモジュール性についての簡単な紹介
前書き
モジュール化に関して、最も直接的に現れるのは、私たちが記述する require キーワードと import キーワードです。関連情報を確認すると、CommonJS、CMD AMD、および RequireJS という用語が必ず出てきます。 SeaJS 見慣れないフレームワークを待つ。たとえば、SeaJS の公式 Web サイトでは、SeaJS について次のように説明されています。「シンプルでフレンドリーなモジュール定義仕様、Sea.js は CMD 仕様に従っています。自然で直感的なコード編成、依存関係の自動読み込み...」
フロントエンドとして初心者です、申し訳ありませんが、理解できません。私のいつものスタイルでは、何かを紹介する前に、それがなぜ必要なのかを必ず説明する必要があります。
JavaScriptの基礎
クライアント側で作業する学生は、OCの#import "classname"、Swiftのモジュールおよびファイル修飾子、およびJavaのインポートパッケージ+クラスモードに精通している必要があります。私たちは、ファイルの参照がクラスを参照するというパターンに慣れています。ただし、JavaScript のような動的言語では、状況が変わります。例:
<html> <head> <script type="text/javascript" src="index.js"></script> </head> <body> <p id="hello"> Hello Wrold </p> <input type="button" onclick="onPress()" value="Click me" /> </body> </html>
// index.js function onPress() { var p = document.getElementById('hello'); p.innerHTML = 'Hello bestswifter'; }
HTML の 3f1c4e4b6b16bbbd69b2ee476dc4f83a タグは import として理解できるため、ボタンの onclick イベントは、index.js の onPress の定義を呼び出すことができます。関数。
プロジェクトが発展するにつれて、クリックされたテキストを動的に生成し、他の JS ファイルによって生成する必要があることが判明したとします。その場合、単純なindex.js ではその仕事を行うことができません。生成されたコンテンツは math.js ファイルで定義されていると仮定します:
// math.js function add(a, b) { return a + b; }
クライアントの考えによると、この時点の Index.js ファイルは次のように記述されるはずです:
// index.js import "math.js" function onPress() { var p = document.getElementById('hello'); p.innerHTML = add(1, 2); }
残念ながら、JavaScript はインポート メソッドをサポートしていませんつまり、他の JS ファイル内のメソッドを 1 つの JS ファイル内で参照することはできません。正しい解決策は、index.js で add メソッドを直接呼び出し、index.html で math.js を参照することです。
<html> <head> <script type="text/javascript" src="index.js"></script> <script type="text/javascript" src="math.js"></script> </head> <body> <p id="hello"> Hello Wrold </p> <input type="button" onclick="onPress()" value="Click me" /> </body> </html>
// index.js function onPress() { var p = document.getElementById('hello'); p.innerHTML = add(1, 2); }
この書き方は、Index.js が他の JS のコンテンツに応答しないことがわかります。 add メソッドを呼び出せるかどうかは、独自の HTML ファイルが他の JS ファイルを正しく参照しているかどうかによって決まります。
初期モジュール化
今述べた問題点は、実際には 2 つのタイプに分類できます:
1.index.js はインポートできず、HTML 参照
2.index に依存しています。 js では add メソッドのソースを区別できず、名前空間の概念がありません
最初の質問は後で答えます。たとえば、最初に関数をオブジェクトに入れてみましょう。オブジェクトを使用すると、ユーザーはこのオブジェクトの複数のメソッドを呼び出すことができます:
//index.js function onPress() { var p = document.getElementById('hello'); p.innerHTML = math.add(1, 2); } //math.js var math = { base: 0, add: function(a, b) { return a + b + base; }, };
名前空間の簡略化されたバージョン (つまり、math) をindex.js で指定できることがわかります。ただし、小さな問題がまだあります。たとえば、base 属性は外部に公開され、変更される可能性もあります。したがって、より良い方法は、クロージャで数学を定義して内部プロパティを非表示にすることです:
// math.js var math = (function() { var base = 0; return { add: function(a, b) { return a + b + base; }, }; })();
これまで、モジュールの定義と使用を実装しました。ただし、モジュール化の本質の 1 つは名前空間にあります。つまり、数学モジュールはグローバルではなく、オンデマンドでインポートされることを望んでいます。この方法では、複数のファイルが同じ名前のオブジェクトを公開している場合でも、何も存在しません。問題。 Node.js と同様に、公開する必要があるモジュールは独自のエクスポート コンテンツを定義し、呼び出し元は require メソッドを使用します。
実際、node.js の動作モードを単純にシミュレートし、中間層を追加することで問題を解決できます。 まずグローバル変数を定義します:
// global.js var module = { exports: {}, // 用来存储所有暴露的内容 };
次に、オブジェクトを math.js で公開します:
var math = (function() { var base = 0; return { add: function(a, b) { return a + b + base; }, }; })(); module.exports.math = math;
Userindex.jsはい:
var math = module.exports.math; function onPress() { var p = document.getElementById('hello'); // math p.innerHTML = math.add(1, 2); }
既存のモジュール式ソリューション
上記の単純なモジュール式アプローチには、いくつかの小さな問題があります。まず、index.js は math.js の実行に厳密に依存する必要があります。これは、math.js が実行された後でのみ、index.js 自体がグローバル module.export に登録されるためです。これには、開発者が js ファイルの読み込み順序を手動で管理する必要があります。プロジェクトが大きくなるにつれて、依存関係の維持はますます複雑になります。
次に、JS ファイルをロードするとブラウザーが Web ページのレンダリングを停止するため、JS ファイルの非同期オンデマンド読み込みも必要になります。
最後の問題は、前に示した単純化されたモジュール化ソリューションではモジュールの名前空間が解決されないことです。同じエクスポートによって以前のコンテンツが置き換えられることになります。解決策は、「ファイル パス<--> ;エクスポート コンテンツ」テーブルを維持することです。ファイルパスに基づいてロードします。
上記のニーズに基づいて、モジュール式ソリューションの多くのセットが市場に登場しています。なぜ複数の標準が存在するのかというと、実はフロントエンドの特性が原因です。統一された規格がないため、前述のexportやrequireなどのことを行う際に、誰もが規約に頼ってしまうケースが多くあります。コードの提供者がエクスポート内容を module.exports に格納し、ユーザーが module.export を読み込んだとしても、当然のことながら無駄になります。それだけでなく、各仕様の実装方法や使用シナリオも異なります。
CommonJS
比较知名的规范有 CommonJS、AMD 和 CMD。而知名框架 Node.js、RequireJS 和 Seajs 分别实现了上述规范。
最早的规范是 CommonJS,Node.js 使用了这一规范。这一规范和我们之前的做法比较类似,是同步加载 JS 脚本。这么做在服务端毫无问题,因为文件都存在磁盘上,然而浏览器的特性决定了 JS 脚本需要异步加载,否则就会失去响应,因此 CommonJS 规范无法直接在浏览器中使用。
AMD
浏览器端著名的模块管理工具 Require.js 的做法是异步加载,通过 Webworker 的 importScripts(url); 函数加载 JS 脚本,然后执行当初注册的回调。Require.js 的写法是:
require(['myModule1', 'myModule2'], function (m1, m2){ // 主回调逻辑 m1.printName(); m2.printName(); });
由于这两个模块是异步下载,因此哪个模块先被下载、执行并不确定,但可以肯定的是主回调一定在所有依赖都被加载完成后才执行。
Require.js 的这种写法也被称为前置加载,在写主逻辑之前必须指定所有的依赖,同时这些依赖也会立刻被异步加载。
由 Require.js 引申出来的规范被称为 AMD(Asynchronous Module Definition)。
CMD
另一种优秀的模块管理工具是 Sea.js,它的写法是:
define(function(require, exports, module) { var foo = require('foo'); // 同步 foo.add(1, 2); ... require.async('math', function(math) { // 异步 math.add(1, 2); }); });
Sea.js 也被称为就近加载,从它的写法上可以很明显的看到和 Require.js 的不同。我们可以在需要用到依赖的时候才申明。
Sea.js 遇到依赖后只会去下载 JS 文件,并不会执行,而是等到所有被依赖的 JS 脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。
由 Sea.js 引申出来的规范被称为 CMD(Common Module Definition)。
ES 6 模块化
在 ES6 中,我们使用 export 关键字来导出模块,使用 import 关键字引用模块。需要说明的是,ES 6 的这套标准和目前的标准没有直接关系,目前也很少有 JS 引擎能直接支持。因此 Babel 的做法实际上是将不被支持的 import 翻译成目前已被支持的 require。
尽管目前使用 import 和 require 的区别不大(本质上是一回事),但依然强烈推荐使用 import 关键字,因为一旦 JS 引擎能够解析 ES 6 的 import 关键字,整个实现方式就会和目前发生比较大的变化。如果目前就开始使用 import 关键字,将来代码的改动会非常小。