Babel 7がリリースされたということで、気になる点について調査を行っていたのですが、その過程で一部のプラグインにloose
モードがあるということを知ったので、それについてまとめてみます。
loose
モードとは
JSXのようなそもそもJavaScriptにないものは別として、Babelのプラグインが変換する各文法構造については、EcmaScriptとして厳密な動作仕様が決まっています。
ただ、変換して実行する過程で、「厳密に言えば仕様どおりになっていなくても、重箱の隅はつつかないから処理が早い/コードが短い、実用充分なコードになればそれでいい」という需要もあります。そういうのに対応するのがloose
モードです。
逆に、「厳密な動作のほうが需要が少ない」と判断された場合には、spec
で厳密な動作に切り替える、という仕組みのものもあります。
Object.defineProperty
と代入の違い
loose
モードでは、プロパティの設定をObject.defineProperty
([[DefineOwnProperty]]
)ではなく代入([[Put]]
)で簡略化していることがあります。代入ではenumerable
などdescriptorが設定できないこと以外に、以下の違いがでます。
- プロトタイプチェーンにある同じプロパティが
writable:false
の場合→代入に失敗します。 - プロトタイプチェーンにセッターがある場合→代入はセッターを駆動させます。
具体的な内容
ここからは、プラグインごとにloose
やspec
モードにしたときにどうなるかを並べていきます。
@babel/plugin-transform-arrow-functions
これはArrow Functionを普通の関数に書き換えるプラグインですが、spec
モードにすると以下のような挙動になります。
- コンストラクタとして使うとエラーを発するようなチェックを入れる
- 名前を振る
-
this
をリネームではなく.bind(this)
で再現する
Function.prototype.name
に頼るのでなければ、毎回チェック処理が挟まるデメリットのほうが大きそうです。
@babel/plugin-transform-classes
これはclass
構文を従来の形に書き直すものですが、loose
モードではメソッド定義をObject.defineProperty
ではなく代入で行います。本来false
になるはずのenumerable
がtrue
となるので要注意です。
@babel/plugin-computed-properties
これはComputed Property({[foo()]: bar}
のような、角カッコでくくってオブジェクトリテラルのキーを動的に指定できる機能)の変換を行うものですが、こちらもloose
モードではプロパティの設定をObject.defineProperty
ではなく代入で行います。
これはオブジェクトリテラルにしか使えない機能である以上、Object.prototype
を汚染している場合でもなければ、差は出ないと思います。
@babel/plugin-transform-destructuring
これは分割代入を処理するプラグインですが、loose
を指定した場合は「配列以外のiterable
を想定しない」という挙動になります。オブジェクトや配列にしか使わないなら影響はありません。
@babel/plugin-transform-for-of
これは、for-of
を変換するためのプラグインです。loose
モードでは、「配列だけ別枠で認識して高速化する」代わりに「イテレーターをfor-of
している途中に例外で打ち切られると、イテレーターのreturn
が呼ばれない」という挙動になります。なお、配列以外にfor-of
を使わない場合は、assumeArray
という設定もできます。
@babel/plugin-transform-parameters
これは、引数のデフォルト値やrest引数、分割代入などを変換するためのプラグインです。loose
を使うと、デフォルト引数のある関数のarityが仕様と変化することになりますが、引数をarguments
に頼らず処理できるようになって効率化する、とのことです。
@babel/plugin-transform-spread
これは、引数や配列などで、iterableの...
による展開を処理するためのプラグインです。loose
にすると、配列専用になります。
@babel/plugin-transform-template-literals
これは、バッククオートで区切ったテンプレートリテラルを変換するためのプラグインです。loose
では、concat
ではなく+
で文字列を組み立てるようになります。Symbol.toPrimitive
の設定されたオブジェクトを処理する場合に違いが出る、とのことです。
@babel/plugin-transform-typeof-symbol
これは、Symbol
に対するtypeof
が"symbol"
を返すようにするためのプラグインです。プラグイン自体にloose
の設定はないのですが、@babel/preset-env
にloose
を指定すると、プラグインごと省略されます。typeof
の値を"function"
や"undefined"
などと比較するだけであれば、影響は出ません。
@babel/plugin-proposal-object-rest-spread
これは、オブジェクトに対して...
で展開するようなものを実装するためのプラグインです。これもloose
モードではプロパティの設定をObject.defineProperty
ではなく代入で行います。getterやsetterのあるオブジェクトなどでは違いがでてくることがあります。
@babel/plugin-proposal-class-properties
これはReactでよく見かける、プロパティの定義をクラス内に直接書いてしまえる文法の変換用プラグインです。これまたloose
モードではプロパティの設定をObject.defineProperty
ではなく代入で行います。親クラスにwritable:false
のプロパティやセッターがあると結果が違ってきます(これは仕様上もenumerable
はtrue
です)。
@babel/plugin-proposal-nullish-coalescing-operator
左辺がnull
かundefined
なら右辺を、そうでなければ左辺を返す、??
演算子を処理するプラグインです。loose
とした場合、判定を== null
で済ませます(影響する値は例のdocument.all
だけです)。
@babel/plugin-proposal-optional-chaining
CoffeeScriptにもあった、左辺がnull
でもundefined
でもないときだけプロパティ参照を行う、?.
演算子を処理するプラグインです。loose
とした場合、こちらも判定を== null
と、document.all
を考慮しない形にします。