はじめに
基本は備忘録
モジュールの使用方法でtype="module"
を紹介しているが、基本的にはWebpack
等でビルドすることが前提なので本番環境では紹介した方法は用いない
モジュール
現代のJSは1つのファイルに全てのコードを記述するのではなく、機能ごとに分割して管理する
この分割jsファイルをモジュールという
利点
名前問題の解決(name space)
モジュールを組み合わせて機能を実装できるようになった
CommonJS(CJS)
nodeJS
上でモジュールを管理する仕組み
require
/ exports
で読み込みや書き出し
ECMAScriptESM
ESの仕様に基づくモジュール管理システム
import
/ export
で読み込みや露出
CJCとESMの違い
ESM | vs. | CJS |
---|---|---|
import/export | - | require/exports |
Browser | - | Node.js |
.mjs | - | .cjs |
Import / Export
Import
モジュールの読み込み
Export
モジュールの露出
ESM読み込み方法
基本は通常のjsファイルと同様だが、type="module"
を記述必要がある
モジュール使用方法
// module側の記載
export let publicVal = 0;
export function publicFn() {
console.log('publicFn called')
}
//実行側の記載
import {publicVal, publicFn } from './moduleA.js';
console.log(publicVal);
publicFn()
import
した際に使用する際の名前を変更する
import{元の名前 as 使用したい名前}
import {publicVal as val, publicFn as fn } from './moduleA.js';
defaultを使用した例
1つのファイルで一つ?
// module側
export let publicVal = 0;
//defaultでexportする記述
export default 0かpublicVal;//なんでも良き
//実行側の例
import defaultVal, {publicVal as val} from './moduleA.js';
console.log(val);
console.log(defaultVal)
//defaultでexportする場合{}で囲まない。
//名前はimport時に自由に決めることができる
* as オブジェクト名 from...
* as オブジェクト名 from...
の記法がある
モジュールのexportとdefaultをまとめて記述可
// module側
export let publicVal = 0;
export function publicFn() {
console.log('publicFn called')
}
export default 0;
// 実行側
import * as moduleA from './moduleA.js';
console.log(moduleA)// オブジェクトにmoduleAの変数や関数が格納
console.log(moduleA.default)// 0
プリミティブ型のexport
モジュールでexportしたプリミティブ型の値の変更はできない
理由: モジュールのプリミティブ型の値が格納された変数をimportする際は値自体がコピーされる
そのため、データ不整合が起こりエラーが出る
解決策: オブジェクトにすると参照をコピーするため同じオブジェクトを扱う
そのため、値の変更ができる
モジュールコンテキストとモジュールスコープ
モジュールコンテキスト
ESmodule
を使用した場合global-context
がmodule-context
に変わる
違いが1点だけ
module-contextではthis
が使用できない
console.log(this)// 参照不可
//オブジェクトではないのでレキシカルスコープを辿って
//グローバルスコープのthisを参照しようとする
//上記のconsole.logと一緒なのでthisは参照不可
function fn() {
console.log(this)
}
const obj = {
fn() {
console.log(this)
}
}
//オブジェクトから呼び出したthisは呼び出し元を参照する
obj.fn()// objの中身を参照
console.log(window)//直接参照すればwindowオブジェクトも参照できる
モジュールの場合には関数として読んだ場合にはthisがundefinedになる
<script type="module">
const obj = {
name: 'tom',
hello() {
console.log(this.name); // このthisなobjになる。
function justFunction() {
console.log(this); // このthisはundefinedになる。
}
justFunction();
}
}
obj.hello();
</script>
モジュールスコープ
ESmodule
を使用した場合script-scope
がmodule-scope
に変わる
モジュールのmosule-scope
で宣言した変数は実行側にファイルを読み込んでいてもexport / import
処理をしていない場合利用することはできない。
グローバルオブジェクトは利用できる
// module側
let a = 0;
window.b = 0;
// 実行側
// fromを書いていないが、import処理がないなら必要ない
import 'js/moduleA.js'
console.lor(a)// undefined importしないと利用できない
console.log(window.b)// 0 参照できる
windowを通して変数や関数を利用するのはモジュールの設計思想から外れる
可能ではあるが、使用は推奨されておらず、import / exportを利用する
moduleの特徴
type="module"
をつけるとデフォルトでdefer
属性が付与される
通常のJSファイルは読み込んだ分実行されるが、module
は何度読み込んでも最初の一回のみ実行
import
も同じで何度モジュールをimport
しても実行されるのは最初の一度だけ
nomodule
を記載するとmoduleが対応していないブラウザでのみ実行される
<script nomodule>alert('no module')</script>
module属性
を付与するとデフォルトでStrict
モードになる
moduleは即時関数に似ている
一度だけ実行し、露出したものだけ後から何度でも実行可能
Strictモード
通常のJavaScriptで許容されている一部の書き方を制限する
module
はデフォルトでStrictモード
Strictモードの目的
意図しないバグの混入の防止
予約後の確保
コードのセキュア化
など
Strictモードの有効化
use strict
ファイルの先頭、もしくは関数内の先頭行に記述
実際の例
本来ならエラーが出ない記述をエラーにする
//関数スコープで定義するはずがgl-s
//に定義していてエラーにつながる可能性
function fn() {
a = 0; // windowオブジェクトに格納
}
fn();
console.log(a)
use strict
をつけるとlet
やconst
なしだとエラーを出せる
予約語を確保
const implements, interface, package
将来JSの仕様変更で使われる可能性のある単語で
変数宣言しようとするとエラーになる
セキュア(安全な 堅牢な)
例としてthis
通常はglsで定義したthis
はwindow
を参照する
strictモード
の場合undefined
にして不用意にwindowオブジェクトは閲覧できないようにしてセキュリティを高めている
strictモード
のthisはプリミティブ型の値も保持できる
通常this
はオブジェクトへの参照を保持している
function fn() {
//'use strict'
return this;
}
console.log(fn.call(2)) //2を保持したオブジェクト Number{2}
Strictモード
の場合
function fn() {
'use strict'
return this;
}
console.log(fn.call(2)) // 2
他にも挙動の違いはあるが例として上記のような違いがある
今後JSのアップデートはstrictモードに合わせる形で行われていくと予想される
Strictモードとclassの関係
class
のconstructor
やmethod
内はデフォルトでstrictモード
になっている
ダイナミックインポート
新しい方法なのでブラウザ対応必須
非同期でimportすることができる
// 通常のimport
import { publicVal } from 'ファイル名';
publicFn()
// ダイナミックインポート
import('ファイル名')
.then((modules) => {
modules.publicFn();
})
いつ使うのか?
画面の初期表示時に必要のないコードなどを必要になった時にダイナミックインポートを使って呼び出すことで
非同期で処理することができる
必要のない処理を非同期で行うことによってユーザーをなるべくまたせずに初期表示を行うことができる
Promiseが返るのでasyncで利用可能
// async / awaitの書き換え
async function fn() {
const modules = await import('ファイル名');
modules.publicFn();
}