序文
最近、元同僚と雑談したんですが、面接に行ったときに面接官にフロントエンドのモジュール性について質問されたのですが、その答えは面接官の「言ったんですが、言ってないようです」でした。と大打撃を受け、人生について疑問を感じている私に、フロントエンドのモジュール性とは何ですか? と尋ねました。知っているような気がするが、その理由は説明できないようだったので、ここにモジュール化に関する内容を載せておきますので、一緒に見てみましょう。
1. モジュール化の概要
複雑なコードを機能ごとに複数のモジュールに分割し、個別に保守することで、開発効率の向上と保守コストの削減を実現します。モジュール化は単なるアイデアや理論であり、具体的な実装を含むものではありません。
2. モジュール性の誕生
簡単に言うと、1人ではできないことを複数人に分けて、各人が1つのコンテンツ(モジュール)を担当し、最終的に各人がやったことをまとめて全体にするというものです。
フロントエンドのモジュール化の進化には多くの段階がありますが、最終的な目標は、モジュールを分割し、開発作業を分割することです。全員がモジュールを完成した後、呼び出し元にいくつかのパラメーターとメソッドを公開します。進化の道筋は次のとおりです。よりエレガント、高度に分離、高度な互換性などの方向で行を最適化する方法に従うだけです。
3. モジュール性の進化
第一段階:ファイルのみに基づいてモジュールを分割する方法
具体的な方法は、各機能とそれに関連するステータスデータを別々のファイルに配置することです。各ファイルは独立したモジュールであることが合意されています。モジュールを使用するには、このモジュールをページに導入し、ページ内でモジュールを直接呼び出します。モジュールのメンバー (変数/関数) 短所: すべてのモジュールはグローバルに直接動作し、プライベート スペースがなく、すべてのメンバーはモジュールの外部からアクセスまたは変更でき、モジュールが多すぎると名前の競合が発生しやすく、モジュール間の依存関係を管理できません。
第 2 段階: 各モジュールはグローバル オブジェクトを公開し、すべてのモジュール メンバーはこのオブジェクトにマウントされます。具体
的なメソッドは第 1 段階に基づいており、各モジュールをグローバル オブジェクトに「ラップ」することで、少し追加するような感じです。 「名前空間」をモジュール内のメンバーに割り当てます。名前の競合の可能性は「名前空間」によって軽減されますが、プライベート空間はなく、すべてのモジュール メンバーはモジュール外からアクセスまたは変更することもでき、モジュール間の依存関係は管理できません。
第 3 段階: 即時呼び出し関数式 (IIFE) を使用して、モジュールにプライベート スペースを提供します。具体
的な方法は、関数によって提供されるプライベート スコープに各モジュール メンバーを配置することです。外部メンバーに公開する必要があるメンバーについては、グローバル オブジェクトにプライベート メンバーをぶら下げることによって、プライベート メンバーの概念を実装します。プライベート メンバーには、モジュール メンバー内のクロージャを介してのみアクセスできます。
第 4 段階: IIFE パラメータを依存関係宣言として使用する
具体的な方法は、即時実行関数のパラメータを使用して、第 3 段階に基づいてモジュールの依存関係を転送することです。これにより、各モジュール間の関係がより明確になります。
第 5 段階: モジュラー仕様
Require.js は、AMD モジュラー仕様と自動モジュール ローダーを提供します。モジュラー仕様の出現に続き、CommonJS や CMD などのさらに多くの標準が続きました。。。
4. モジュール仕様の登場
必須: モジュラースタンダード + モジュールローダー
CommonJS 仕様 (nodejs によって提案された一連の標準)
標準: ファイルはモジュールです。各モジュールには個別のスコープがあります。メンバーは module.exports を通じてエクスポートされます。モジュールは require 関数を通じてロードされます。
短所: CommonJS は同期モードでモジュールをロードし、ノードの実行メカニズムは起動時にモジュールをロードします。実行中にモジュールをロードする必要はなく、使用するだけです。ノードでは問題ありません。ただし、ページのロードはブラウザ側で大量の同期リクエストが発生し効率が低い
AMD (Asynchronous Module Definition) --- 非同期モジュール定義仕様
モジュールはdefine関数を通じて定義されます。
利点: 現在、ほとんどのサードパーティ ライブラリが AMD 仕様をサポートしています。
欠点: 複雑なモジュールを使用して慎重に分割し、モジュール JS ファイルが頻繁に要求されるようになります。
Sea.js (タオバオが立ち上げ) + CMD (共通モジュール定義仕様)
CMD 仕様は CommonJS 仕様に似ており、後に Require.js と互換性がありました。
Require.js
AMD モジュール化仕様を提供し、自動モジュール ローダーがモジュールをロードするために必要な機能を提供します
5. モジュールのデフォルト仕様
ブラウザ環境は ES モジュールを使用します
。nodejs は CommonJS を使用します。
6. ESモジュールについて
属性 type = module をスクリプトに追加して ES モジュールを使用します
ESM は自動的に strict モードを採用し、「use strict」を無視します。
各 ES モジュールは個別のプライベート スコープで実行されます。ESM
は CORS を通じて外部 JS モジュールを要求します。ESM
の script タグによりスクリプトの実行が遅延します。
日常のフロントエンド開発の場合, 実際、最も一般的に使用されているのは es モジュールです。ここでは、この仕様の一般的な記述方法をいくつか簡単に紹介します。
ESモジュールのエクスポート
単一エクスポート
export const name = 'foo module'
export function hello () {
console.log('hello')
}
マージエクスポート
const name = 'foo module'
function hello () {
console.log('hello')
}
class Person {}
export { name, hello, Person }
エクスポートをマージして名前を変更する
const name = 'foo module'
function hello () {
console.log('hello')
}
class Person {}
export {
name1: name,
hello2: hello,
Person3: Person
}
デフォルトのエクスポート
const name = 'foo module'
function hello () {
console.log('hello')
}
class Person {}
export default name;
ES モジュールをインポートおよびエクスポートする際の注意事項 リテラルのエクスポートとモジュールのエクスポートの違い。
リテラル (オブジェクトなど) をエクスポートします。export default { name, age }
注: import {name, age} from 'modulename' インポートされたモジュールでは、name と age の値を使用できません。
エクスポートモジュール:
export { name, age }
import {name, age} from 'modulename' インポートされたモジュールは name と age の値を使用できます。
理由: import はモジュールの内部使用をインポートします。
エクスポートモジュールリファレンス
注: エクスポートはモジュールの参照関係 (アドレス) を公開し、読み取り専用で変更できません (変更しようとするとエラーが報告されます --- Uncaught TypeError: Assignment to constant variable)
注意点
- CommonJS では、最初にモジュール全体がオブジェクトとしてインポートされ、次に必要なメンバーがオブジェクトから構築されます const { name, age } = require('./module.js')
- ES モジュールの { } は固定構文であり、モジュールのエクスポート メンバー import { name, age } を './module.js' から直接抽出することを意味します。
- メンバーのインポートはコピーを作成するのではなく、モジュール メンバーの参照アドレスを直接インポートするため、インポートで取得した変数とエクスポートでインポートした変数はメモリ上の同じ空間にあります。モジュール内のメンバーが変更されると、それらも同時に変更されます。
- インポートされたモジュールのメンバー変数は読み取り専用です。 name = 'tom' // エラー
- インポートされるのはオブジェクトであり、オブジェクトの属性の読み書きには影響しません。 name.xxx = 'xxx' // 通常
ESモジュールのインポート
インポートファイルのパス
import('./module.js').then(function (module) {
//所有模块成员都在module参数里
})
- 引用される名前
- 相対パスの./は省略できません
- 絶対パスと完全な URL を使用できます
モジュールのインポート時にモジュールメンバーを抽出するかどうか
- モジュールをインポートし、モジュール メンバーを抽出します。 import {} from './module.js'
- モジュールをインポートしてもモジュール メンバーは抽出されません import './module.js' (外部制御を必要としないサブ機能モジュールをインポートする場合に便利です)
モジュールの複数またはすべてのメンバーを同時にインポートする
import * as mod from './module.js' は、抽出されたすべてのメンバーをオブジェクトに入れる必要があります。as を介して、インポートされたメンバーはオブジェクト属性として表示されます。
モジュールの動的インポート (インポート前に特定の条件を満たす必要がある場合に利用可能)
import ('./module.js') は、Promise がモジュール メンバーを取得する方法を返します。
import('./module.js').then(function (module) {
//所有模块成员都在module参数里
})
名前付きメンバーとデフォルトメンバーの両方をインポートします
import { name, age,default as other} from './module.js'
または
import other, { name, age} from './module.js'other は、モジュール module のデフォルトでエクスポートされたすべてのメンバーを表します
Node.js の ES モジュール
Node.js の ES モジュール - CommonJS との対話
CommonJS モジュールを ES モジュールにインポートできます
es-モジュール.mjs
import mod from './commonjs.js'
console.log(mod)
メンバーを直接抽出することはできません。インポートによってエクスポートされたオブジェクトが分解されないことに注意してください。
import { foo } from './commonjs.js'
console.log(foo)
export const foo = 'es module export value'
CommonJS モジュールは常に 1 つのデフォルト メンバーのみをエクスポートします。
共通js.js
module.exports = {
foo: 'commonjs exports value'
}
exports.foo = 'commonjs exports value'
CommonJS モジュールの require を通じて ES モジュールをロードできない
const mod = require('./es-module.mjs')
console.log(mod)
Node.js の ES モジュール - CommonJS との違い
例:
// 加载模块函数
console.log(require)
// 模块对象
console.log(module)
// 导出对象别名
console.log(exports)
// 当前文件的绝对路径
console.log(__filename)
// 当前文件所在目录
console.log(__dirname)
1、ESM 中无法引用CommonJS中的那些模块全局成员
2、require, module, exports 可通过import 和export 代替
3、__filename 和 __dirname 通过import 对象的 meta 属性获取
const currentUrl = import.meta.url
console.log(currentUrl)
// 通过 url 模块的 fileURLToPath 方法转换为路径
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)
Node.js の ES モジュール - 新しいバージョンでのさらなるサポート
- Node v12 以降のバージョンでは、package.json の module に type フィールドを追加することで、デフォルトのモジュール システムを ES モジュールに変更できます。このとき、ファイル拡張子を .mjs に変更する必要はありません。
- type=module で CommonJS を引き続き使用する必要がある場合は、ファイル拡張子を .cjs に変更する必要があります。
パッケージ.json:
{ "type": "module" }
Node.js の ES モジュール - Babel 互換ソリューション
babel をインストールします:yarn add @babel/node @babel/core @babel/preset-env --dev
babel-node テストを実行します:yarn babel-nodeindex.js --presets=@babel/preset-env
**.babelrc 設定**
{ "plugins": [ "@babel/plugin-transform-modules-commonjs" ] }
7. まとめ
質問は一般的なものから詳細なものまで
行われますが、面接でモジュール化に関する質問があった場合は、まず大まかな概念を答えてから、追加の質問がある場合は詳細を踏まえて回答するようにしてください。
ヒント: あなたにインタビューする人は、あなたが言わなければならないことを一度に深く理解する必要はありません。シンプルで理解しやすい現地語を使用し、明確で一貫性のある言葉を使って、最初にそれを投げることができます。例: XXXX と YYYY の違いは何
ですか? 答え: 違いは 3 つあります。
Q: フロントエンドのモジュール化について話してもらえますか
? A: モジュール型開発手法により、コードの再利用率が向上し、コード管理が容易になります。通常、ファイルはモジュールであり、独自のスコープを持ち、特定の変数と関数のみを公開します。
Q: モジュール化の基準は何ですか?
回答: 現在一般的な js モジュール化仕様には、CommonJS、AMD、CMD、ES6 モジュール システムが含まれます。
Q: ES モジュールと CommonJS の違いは何ですか? 回答
:
- 構文レベルを使用すると、CommonJs は module.exports、エクスポートにはエクスポート、インポートには require を使用します。EESModule はエクスポートにエクスポート、インポートにインポートを使用します。
- CommonJs は実行時にモジュールを読み込み、ESModule は静的コンパイル中にモジュールの依存関係を決定します。
- ESModule はコンパイル中にすべてのインポートを最上位に上げますが、CommonJs は require を上げません
- CommonJs がエクスポートするのは値のコピーであり、読み込み結果はキャッシュされるため、内部で値が変更されると外部には同期されません。ESModule はエクスポートされた参照であり、内部の変更を外部に同期できます。
- CommonJs のトップレベルの this はモジュール自体を指しますが、ESModule のトップレベルの this は未定義を指します
- CommonJS はモジュール全体とすべてのインターフェイスをロードしますが、ESModule はインターフェイスの 1 つを個別にロードできます。