echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【JavaScript】ES2022で正式採用される機能について

本題

JavaScriptの仕様であるECMAScript(=ES)について、そろそろ最新版のES2022が承認される、というのをICSさんのブログ(「参考サイト」参照)で知りました。

なので今回は、ES2022で正式採用される機能について、コード例を挙げて説明したいと思います。

なお今回紹介する機能ですが、Node.jsの場合、Node Greenを見るとわかる通り、2022/6/18現在のLTSであるver16.15.1では、普通に全機能が使用可能です。(ブラウザでもほとんどの機能は使用可能の模様)

参考サイト

紹介する機能一覧

  • クラス以外の機能
    • top-level await
    • .at() method on the built-in indexables
    • Object.hasOwn
    • Error.cause property
    • RegExp Match Indices
  • クラス関連
    • フィールド・メソッド・アクセスレベル
      • クラスフィールド、static、private
    • Ergonomic brand checks for private fields

クラス以外の機能

top-level await

モジュールの最上位ソースに限り、asyncなどをつけなくても、直接awaitをつけることができるようになりました。
これにより最上位ソースで非同期処理を扱いやすくなりました。

なお、top-level awaitはモジュール(※)限定なので、そうではないJSソースで実行してもエラーになります。
またあくまで最上位ソース限定なので、そうではない場所(例えば関数内など)で実行する場合は、今まで通りasync定義が必要です。

※下記に該当するソ-ス

  • 拡張子*.mjsのファイル
  • package.json"type":"module"が定義されたファイル
  • <script type="module"> が定義されたHTMLスクリプト
import * as axios from 'axios';

// モジュールファイルの最上位のみ、asyncが不要。  
// 下記ソースは正常動作する。
const x = await axios.default.get("https://hogehoge.com/users/1");
console.log(JSON.stringify(x.data));  
  
// 関数内など、トップレベル以外ではasyncが必要。  
// なので下記ソースはエラーになる。 
function badExampleFuncAsync() {
  const x = await axios.default.get("https://hogehoge.com/users/1");
  console.log(JSON.stringify(x.data));  
}  
  

.at() method on the built-in indexables

String及びArray, TypedArrayの要素に、.at(index)でindexの要素にアクセスできます。
「indexの要素にアクセス」自体は元からあるのですが、substr関数のように、負数を指定することで末尾からのindexを指定できます。

これにより、例えば配列の末尾要素を指定する際に、今までのようなarray[array.length -1]というソースは不要になります。

const str = 'hello,es2022';
const array = ['hello', 'es', '2022'];
  
// これは今までと同じ。
console.log(str.at(6));  // e
console.log(array.at(2));  // 2022  
  
// 負数を指定することで、末尾からの要素を指定できる。  
// (-1が末尾要素のindex)
console.log(str.at(-8));  // o
console.log(array.at(-3));  // hello

Object.hasOwn

オブジェクトが該当プロパティを持っているかを判定できる関数です。
というか実際にはObject.prototype.hasOwnProperty.callのショートハンドになります。

ただhasOwnPropertyにはプロトタイプ汚染の問題がある&上記が長くて煩わしいので、それを防ぐ目的だと思われます。

プロトタイプ汚染については、こちらのサイトで詳しく記載されています。

const obj = {
  x: 10,
  y: 20,
};
  
console.log(Object.hasOwn(obj, "x"));  // true
console.log(Object.hasOwn(obj, "z"));  // false

Error.cause property

親子関係(≒依存関係)があるソースでエラーが発生した際、エラーの発生源をたどれるようになる仕組みです。
これはなかなか文章で説明しにくいので、具体的なソースを見た方が早いと思います。

function parent() {
    try {
        child();
    } catch (e) {
        // ここのeはchild()からthrowされたもの。  
        console.log(`e is ${e.message}`);    
        console.log(e.cause.message);     
        console.log(e.cause.cause.message);
    }
}
  
function child() {
    try {
        grandChild();
    } catch (e) {  
        // ここのeはgrandChild()からthrowされたもの。
        // 発生源にe(=grandChild Error)を指定
        throw new Error("child Error", {cause: e});
    }
}
  
function grandChild() {
    try {
        nextGrandChild();
    } catch (e) {
        // ここのeはnextGrandChild()からthrowされたもの。
        // 発生源にe(=nextGrandChild Error)を指定
        throw new Error("grandChild Error", {cause: e});
    }
}
  
function nextGrandChild() {
    // ここがエラー発生源
    throw new Error("nextGrandChild Error");
}
  
parent();

上記ソースを実行すると、下記ログが出力されます。(不要な部分はカット)

// childでthrowされたエラーのメッセージ
e is child Error  
  
// childでthrowされたエラーのcauseに設定されたエラー  
// (=grandChildでthrowされたエラー)のメッセージ
grandChild Error  
  
// grandChildでthrowされたエラーのcauseに設定されたエラー  
// (=nextGrandChildでthrowされたエラー)のメッセージ
nextGrandChild Error

RegExp Match Indices

正規表現でのマッチングについて、dフラグを付けることで、一致した位置の情報を付加することができるようになりました。
一致位置情報は戻り値のindicesプロパティで確認できます。

const result = /cd/d.exec('abcdefabcdefg');
console.log(result);

const result2 = /cd(ef)/d.exec('abcdefabcdefg');
console.log(result2);

上記の実行結果がこちら。

// resultの結果
[
  'cd',
  index: 2,
  input: 'abcdefabcdefg',
  groups: undefined,
  indices: [ [ 2, 4 ], groups: undefined ]
]
  
// result2の結果
[
  'cdef',
  'ef',
  index: 2,
  input: 'abcdefabcdefg',
  groups: undefined,
  indices: [ [ 2, 6 ], [ 4, 6 ], groups: undefined ]
]

indicesのindex[0]に、一致位置の開始位置&終了位置が配列で入ってきます。
なお終了位置はsilce関数で指定するものと同じで、「一致文字列の最終文字のindex+1」となります。
(resultの例(=「cd」)で言えば、「d」のindexである3ではなく、4になる)

indicesのindex[1]以降では、キャプチャグループがある場合にキャプチャグループとの一致位置の開始位置&終了位置が配列入ってきます。
今回の場合、result2でキャプチャグループ「ef」の一致位置情報である配列[4, 6]が返ります。

クラス関連

ここからは、クラス関連の機能になります。
ES2022はクラス関連の機能が多く、やっとクラスの基本的な機能が多言語に追いついたという印象です。

フィールド・メソッド・アクセスレベル(まとめて説明)

クラスの基本的な要素であるフィールド・メソッド・アクセスレベルについて、下記機能が追加されました。

  • クラスフィールドを設定可能になった。
    • メソッド同様、静的(static)フィールドも設定可能
  • クラスメソッドに静的メソッドを設定可能になった
  • privateなフィールド/メソッドを設定可能になった
    • 当然、クラス外からはアクセス不可
    • これでやっと現場レベルのルールで設定する必要がなくなる
class Sample {
    x = 1;  
    // 変数名の前に#をつけると、privateなフィールド/メソッドになる。
    #y = 2;
    
    // staticをつけると、静的フィールド/メソッドになる。
    static sx = 10;
    static #sy = 20;
    
    constructor() {}
  
    // getter  
    // ちなみにgetter/setterもprivateに設定可能。  
    // ただ、利用機会は少ないかも...
    getY() {
        return this.#y;
    }
    
    // privateフィールド&メソッドは、クラス内からのみ参照可能
    getprivateMethodValue() {
        return this.#privateMethod();
    }
    
    // static/privateは、もちろんメソッドにも設定可能
    #privateMethod() {
        return 'I am private method';
    }
    
    static staticPublicFunc(){
        return this.#staticPrivateFunc();
    };
    
    static #staticPrivateFunc(){
        return this.#sy;
    }
}

  
const cls = new Sample();
console.log(cls.x);  // 1
console.log(cls.#y);  // エラー
console.log(cls.getY());  // 2
  
console.log(Sample.sx);  // 10
Console.log(Sample.#sy);  // エラー
  
console.log(cls.#privateMethod());  // エラー
console.log(cls.getprivateMethodValue());  // I am private method
console.log(Sample.#staticPrivateFunc());  // エラー
console.log(Sample.staticPublicFunc());  // 20

なおVSCodeでは、プライベートフィールド/メソッドに外部からアクセスしようとしたところ、ちゃんと怒られました。

Ergonomic brand checks for private fields

「人間工学に基づくプライベートフィールドのブランドチェック」ってなんのこっちゃ?という感じですが、オブジェクトのinプロパティを使って、プライベートフィールド&メソッドを持っているかをクラスレベルで調べられる機能のことらしいです。

これもソースを見た方が早いので、ソースをどうぞ。

class Sample {
    #y = 2;
    constructor(y = null) {
        if(y ?? null) this.#y = y;
    };

    static isSample(obj){  
        // #y in objで、obj内のプライベートフィールド#yを評価する。  
        // なお#yは正確にはthis.#yなので「Sampleクラスの#yフィールドを
        // 持っているかどうか」を判定する。
        // ここではprivateフィールドで行っていますが、privateメソッド及び  
        // 両方を組み合わせることも可能です。  
        return #y in obj;
    }
}

class Sample2 {
    #y = 2;
    constructor() {}
}  
  
// true(クラスが同じなので)
console.log(Sample.isSampleByField(new Sample()));  
  
// false(クラスが違うので)
console.log(Sample.isSampleByField(new Sample2()));  
  
// true(クラスが同じなので。#yの値そのものは問わない)
console.log(Sample.isSampleByField(new Sample(10))); 

感想

今回の感想としては、

  • クラス以外の機能
    • あればあるで便利な機能が増えた
  • クラス関連の機能
    • 基本的な機能が多言語に追いついた

という感じです。

ただtop-level awaitや.at()は地味ですが個人的にはうれしい機能だなと思いました。

またクラスに関しては、やっと普通に使えるようになったなあ...という感じです。(protectedとかあるともっといいんですけどね)

それでは、今回はこの辺で

【Next.js】ブラウザ側で環境変数が参照できない場合の対処法

本題(今回は前置きは省略)

Next.jsで環境変数(.env.*ファイルやコンテナに設定した)をブラウザ側(クライアント側、端的にいえばReactソース上)で参照しようとしても、値が参照できない

TL;DR

内容

Next.jsで、環境変数をブラウザ側(≒クライアント側)で利用しようと思っても、そのままでは利用できません。

例えば下記ソースをブラウザで表示しても、process.env.TEST_ENV_VALUEの部分がundefinedになっているのが分かります。(stateの部分は無視してください)

#.envで環境変数を設定しても...
TEST_ENV_VALUE=hogehoge
export const ComponentDruaga: VFC<ComponentDruagaProps> = ({treasures}) => {
    const {state, changeState}: UseMyStateType = useMyState();

    return (
      <div css={myCss}>
        <div>{`stateは${state}です。`}</div>  
         // ここのprocess.env.TEST_ENV_VALUEがundefinedになっている
        <div>{`TEST_ENV_VALUEは${process.env.TEST_ENV_VALUE}です。`}</div>
        <button onClick={changeState}>stateを変える</button>
      </div>
    )
}

原因

原因ですが、Next.jsではデフォルトでブラウザ側には環境変数が公開されないからです。

ちなみに、これはれっきとしたNext.jsの仕様であり、公式ドキュメントにも明記されています。(SSRだからということもあるのかな?)

https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser

ただ、ブラウザ側でも環境変数の値を参照させたいケースは出てくると思うので、対策をする必要があります。

対策

で、対策なのですが、具体的には下記2つの方法があります。(先程の公式ドキュメント内にも記載されています)

環境変数名のプレフィックスに「NEXT_PUBLIC_」をつける

環境変数名のプレフィックスに「NEXT_PUBLIC_」をつけることで、その環境変数はブラウザ側にも公開されますので、ブラウザ側で参照することができます。

サーバーで実行される関数から環境変数を渡す

デフォルトで環境変数はサーバー側にのみ公開されるので(server-only secrets safe)、サーバー側で実行される関数から環境変数の値をpropsなどで渡してあげれば、値そのものはブラウザ側でも参照することができます。

なお「サーバーで実行される関数」には以下のものがあります。(ただし「APIルート用の関数」はAPIの呼び出しに使うもの(=APIを呼び出さないと使わない)ので、今回の目的には不向きだと思います。

  • getServerSideProps関数
  • getStaticProps関数
  • APIルート用の関数(pages/api以下)
#.envで環境変数を設定して
TEST_ENV_VALUE=hogehoge
NEXT_PUBLIC_TEST_ENV_VALUE=fugafuga
// 例えば、getServerSidePropsでこのように設定する
export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => {
  
    return {
      props: {
        // ここで環境変数の値を設定したキーを返す
        testEnvValue: process.env.TEST_ENV_VALUE
      }
    }
}
  
// Propsには{testEnvValue: string}が設定されているものとする。
const Druaga: VFC<Props> = ({testEnvValue}) => {
    return (
      <>
        <ComponentDruaga testEnvValue={testEnvValue}/>
      </>
    )
}
  
export default Druaga
  
  
export const ComponentDruaga: VFC<Props> = ({testEnvValue}) => {
  
  const {state, changeState}: UseMyStateType = useMyState();
  
  // 下記のように環境変数の参照を変える。  
  //  (環境変数以外の部分は、無視してください)
    return (
     <div css={myCss}>
        <div>{`stateは${state}です。`}</div>
        <div>{`TEST_ENV_VALUEは${testEnvValue}です。`}</div>
        <div>{`NEXT_PUBLIC_TEST_ENV_VALUEは${process.env.NEXT_PUBLIC_TEST_ENV_VALUE}です。`}</div>
        <button onClick={changeState}>stateを変える</button>
      </div>
    )
}

上記のように変更すると、ちゃんと環境変数の値がブラウザ側でも参照できているのが分かります。

締めの言葉

ちょっと簡素ですが、今回は以上です。

【VSCode】VS Code Meetup #19 - フロントエンド開発Nightで登壇しました(トラブル編)

はじめに

前回、「『VS Code Meetup #19 - フロントエンド開発Night』というイベントで『Remote-Containersでnext.js環境を作った話』という内容で登壇」した話を書きました。

前回は登壇内容の補足(Remote-Containersについて&どのようなツールを入れたか)がメインでしたが、今回はトラブル編ということで「環境を構築する際に困ったこと」について書きたいと思います。

内容

  • ts-jestで「Cannot use import statement outside a module」エラーが出る
  • emotionでエラーが出る

ts-jestで「Cannot use import statement outside a module」エラーが出る

現象

TypeScript→JavaScript変換時たまに発生する、ES Module非対応のソースでimport xxx from yyyを行うと発生するエラーです。

対策方法として割とよく知られているのは以下の方法だと思いますが、どれもうまくいかず、困っていました。

  • 拡張子を.mts(.mjs)にする
    • 関連するnode_modulesパッケージもすべて拡張子変更する必要があり、さすがに無理。
    • 自作のものならまだ何とかなるけど...
  • package.json"type":"module"定義を付与する
    • これも上と同様node_modulesパッケージもすべて拡張子変更する必要があり、無理がある。
    • 一応プロジェクト直下のpackage.jsonには定義したが、現象変わらず
  • babelを使う
    • これが一番良い方法かも
    • ただ、色々情報調べて試したが、自分の環境ではうまくいかなかった
解決方法

...と思っていたんですが、ts-jestっていつの間にかES Module対応されていたんですね。(公式Doc読めよ...)

ESM Support | ts-jest

という訳で、下記を行えばOKです。

  • 設定ファイルに以下を追加する
    • ts-jestに対して"useESM": "true"の設定を追加する
    • extensionsToTreatAsEsmに*.tsを追加する

※公式ドキュメントでは「tsconfigのmoduleもES Moduleの値(es2020など)かどうかを確認」と書いてあるが、これはpresetがCommonJSに変換する形式なら不要?

Presets | ts-jest

{
    ...(jestのほかの設定), 
    
    "preset": "ts-jest",
     "globals": {
      "ts-jest": {
        "tsConfig": "tsconfig.json",
        "useESM": true  // これを追加
      }
    },
    // これも追加
    "extensionsToTreatAsEsm": [
      ".ts",
      ".tsx"
    ]
  }

emotionで「インターフェイス 'JSX.IntrinsicElements' が存在しないため、暗黙的に JSX 要素の型は 'any' になります。」というエラーが出る

emotionの公式サイトに従って、下記のようにコードを書きますが、なぜかcssの箇所で「インターフェイス 'JSX.IntrinsicElements' が存在しないため、暗黙的に JSX 要素の型は 'any' になります。」というエラーが発生する現象です。(@emotion/react@emotion/styledはインストール済み)

import { css } from '@emotion/react'  
  
const myCss = css({
  margin: '10px;'
})  
  
export const ComponentDruaga: VFC<ComponentDruagaProps> = ({treasures}) => {
    const {state, changeState}: UseMyStateType = useMyState();

    return (  
       // なぜかここで上記エラーが発生する
      <div css={myCss}>
        <div>{`stateは${state}です。`}</div>
        <button onClick={changeState}>stateを変える</button>
      </div>
    )
}

公式サイトのIntroductionに特に記載があるわけでもないので、最初は分かりませんでした...

解決方法

解決方法ですが、公式サイトのここにあるように@emotion/babel-pluginを導入すれば解決できました。
※公式サイトでもこの@emotion/babel-pluginは強く推奨とあるのですが「version8以上では必須ではない」と書いてあったため、インストールしてませんでした。

@emotion/babel-pluginをインストール後、.babelrcファイルに書きを定義すればOKです。

{
    "presets": [
      [
        "next/babel",
        {
          "preset-react": {
            "runtime": "automatic",
            "importSource": "@emotion/react"
          }
        }
      ]
    ],
    "plugins": ["@emotion/babel-plugin"]
}

ちなみに、下記サイトにめちゃくちゃ分かりやすいemotion+TypeScript環境の作成のやり方が書いてあるので、おススメです。(自分もここを参照しました)

【Next.js & TypeScript】Emotionの導入が大変だったので手順をまとめておく

まとめ

以上、「環境を構築する際に困ったこと」でした。
色々有益な情報が各種ブログなどにあったので、助かりました。

あと教訓として、やはり「公式サイトはまず最初に見ろ!」ですね。はい。(自戒も込めて)

それでは、今回はこの辺で。

【VSCode】VS Code Meetup #19 - フロントエンド開発Nightで登壇しました&内容補足

はじめに

お久しぶりです。

もう1か月以上前になってしまったのですが、2022/4/11(月)に行われた「VS Code Meetup #19 - フロントエンド開発Night」というイベントで、「Remote-Containersでnext.js環境を作った話」という内容で登壇しました。

VS Code Meetup #19 - フロントエンド開発Night - connpass

内容はタイトル通り、VS Code拡張機能であるRemote-Containersを使ってNext.jsの開発環境のベースを構築した話なのですが、質問を受けた&説明しきれなかった部分について、ここで補足しようと思います。

なお登壇資料はこちらです。

www.slideshare.net

Remote-Containerって何?

Remote-Containerとは、「あるコンテナ環境内でVS Codeを開き、開発やテストを可能にする」拡張機能です。
コンテナの中に入る場合、docker exec -it などを使うと思いますが、そういう操作を一切せずに、VS Codeからわずかな操作だけで簡単にコンテナ内部に入ったり、コンテナ内でVS Codeで開発やテストを実行することもできます。

また、コンテナ環境を簡単に作るための情報(ベースのimage, 各種設定など)も用意されているので、コンテナベースでの環境構築・開発を行う際に大変便利です。(ただし、最低限のDockerの知識は必要になります)

ちなみに登壇時に「離れた場所にあるコンテナに入る拡張機能なのか?」という質問を頂いたのですが、Remote Containerはあくまでもローカル(=VSCodeで開いている環境)をコンテナで開く&コンテナに入るための拡張機能です。(離れた場所にあるコンテナに入る場合は、Remote-SSHを用います)

devcontainer.jsonについて

登壇資料内「定義ファイル(devcontainer.json)からコンテナ環境を作成できる」と記載していますが、そのdevcontainer.json についての補足です。

devcontainer.json は、開いている環境をRemote-Containerからコンテナとして開くための設定で、コンテナ情報+αを記載します。
登壇資料では主に1から作成する方法を記載しましたが、docker-compose.ymlDockerfileがあれば、そこから生成することもできます。

コマンドパレットから「Remote-Containers: Add Development Conatiners...」や「Open in Container」などの「コンテナを開く」系のアイテムを選択すると以下のようなメニューが表示されるので、そこから「From (ファイル名)」を選択すればOKです。

ちなみに、Remote-Containerで動かす場合、下記の設定が重要になります。

項目 説明 備考
settings コンテナ生成時に、settings.jsonにある項目についてのデフォルトの設定を行う
extensions コンテナ内で使用する拡張機能を設定する 拡張機能のIDを配列で指定する(※1)
features コンテナ内で使用するツールなどの設定を行う devcontainer.jsonを対話形式で作成した際に追加したツール(登壇資料の9スライド目を参照) (※2)

※1:拡張機能の一覧で右クリックすると、IDをコピー出来ます。また直接devcontainer.jsonのextensionsに追加することもできます。
※2:自分で定義する場合、いくつかの条件に沿う必要がありそう。下記記事に詳しく記載されています。

参考:Dev container featuresについて調べてみる

何のツールをインストールしたのか

「Remote-Containersでnext.js環境を作った話」というタイトルなのに、結局はnpm(yarn) installでの環境構築がメインになってしまったわけですが、参考までにどのツールを入れたかを記載しておきます。(詳細説明は割愛します)

  • create-next-app, typescript
    • このあたりは必須かなと思います。
  • ts-jest, @testing-library/react, @testing-library/react-hooks
    • テスト用のツール群です。(あと、storybookも入れておけばよかったかも)
  • emotion
    • CSS in JSのライブラリ
  • recoil
    • Reactでステート管理を柔軟に行うためのツール。(正式リリース版ではないみたいだが、個人的に使いやすい)
  • SWR
    • HTTP REF 5861で提案された「stale-while-revalidate(再検証中は古いものを)」に基づき、APIのデータを扱うライブラリ

emotion, recoil, swrは単に私が仕事で使ってて、使いやすかったからという理由で入れただけですが...

まとめ

以上、「VS Code Meetup #19 - フロントエンド開発Night」でも私の発表の補足でした。

イベントでの発表だとどうしても時間の関係で細かいところまで説明しきれない部分があるので、こんな感じでブログで補足せざるを得ないですね...本当は登壇前に補足ブログを公開した方がいいのかもしれないですね。(次からできるだけそうします)

本当は登壇から近い日にちに公開できればよかったんですが、個人的に登壇後まもなく入院、手術、その他諸々やらがあり、全く手が付けられませんでした...

ただ、これから徐々にペースを上げていこうと思います。

それでは、今回はこの辺で。

【AWS】IaCツールの比較

はじめに

お久しぶりです。

今までGitやJavaScriptについて書きましたが、今回は、Infrastructure as Code(以下IaCと記載)のツールについての記事です。

IaCツールに関しては色々あると思いますが、今回触れるのは下記の3つです。

  • AWS CDK
  • Terraform
  • Serverless Framework

上記について、自分なりに感じた特徴やメリット・デメリットなどを記載しようと思います。

前提

  • 今回はゼロベース(新規に一から導入する)観点で書いています。
    • 導入&実際に運用後はまた違ってくると思うので
  • 自分の経験の関係上、ターゲットとなるクラウドAWSとなります。

TL;DR

特徴 言語 メリット デメリット 有用なケース
AWS CDK ・プログラミング形式で書ける
AWS公式
各種プログラミング言語 プログラミング言語である プログラミング言語である プログラミングをわかる人がIaCも担当する場合
Terraform ・HCLという独自言語で記載
・公開モジュールを利用できる
HCL(HashiCorp Configuration Language) ・リソースの参照がしやすい
・公開モジュールを利用できる
パラメータ名が一部CloudFormation(以下Cfnと記載)と異なる クラウドインフラ全般をカバーしたい場合
Serverless Framework プラグインで機能拡張ができる
YAML/JSONで記載できる
YAML/JSON ・Lambda関連が非常に便利
プラグインで機能拡張できる
Lambda関連以外の部分の記載 Lambdaを多く使用する場合

AWS CDK

特徴

プログラミング言語で書ける
AWS CDKはプログラミング言語でコーディングして記載するのが特徴です。(メジャーなのはTypeScriptかな?)
そのため、何かしらの言語を知っていれば、導入の敷居はあまり高くないと思います。

AWS公式ツールである
AWS CDKはAWS公式が出しているツールなので、その点で(謎の)安心感があるかもしれません。(だから何?と言えばそれまでなのですが)

メリット

プログラミング言語で記載
先述の通り、AWS CDKはプログラミング言語で記載するので、いずれかの言語の知識があれば、導入はそれほど難しくありません。
そのため、アプリエンジニアがそのままAWS CDKでIaCを導入する...といったこともできます。

また、プログラミング言語に慣れている人からすれば、YAMLJSONより構造的で分かりやすくなるかな?という点も個人的にはメリットだと思います。

デメリット

プログラミング言語で記載
メリットと同じ内容ですが、裏を返せば、プログラミング何もわからん...という場合は、導入の敷居が高いかもしれません。

有用なケース

プログラミングをわかる人がIaCも担当する場合
ある程度対応するプログラミング言語がわかるメンバー(アプリエンジニアなど)が複数いる場合、AWS CDKは下記観点で有用だと思います。

  • プログラミング言語で書ける(=導入の敷居が高くない)
  • 複数のメンバーがメンテ対応できる(=属人化を防ぐ)

Terraform

特徴

HCLという独自言語で記載
TerraformはHCL(HashiCorp Configuration Language)という独自記法で記載します。(下記コードを参照)
といっても、そんなに難しいものではないので、そこまで導入に時間はかからないのではと思います。

# S3バケットを定義
resource "aws_s3_bucket" "b" {
  bucket = "my-tf-test-bucket"

  tags = {
    Name        = "My bucket"
    Environment = "Dev"
  }
}

# S3バケットポリシーを定義
resource "aws_s3_bucket_policy" "allow_access_from_another_account" {
  bucket = aws_s3_bucket.b.id
  policy = data.aws_iam_policy_document.allow_access_from_another_account.json
}

公開モジュールを利用できる
Terraformのモジュール(詳しくは前回の記事を参照)は、下記リンク先(公式サイト内)で公開されているものも多く、これらを使うことで、わざわざ自作せずとも、そのまま公開モジュールを利用できます。
そうすることで、定義作成の手間を軽減することができます。

https://registry.terraform.io/browse/modules?provider=aws

メリット

リソースの参照がしやすい
Terraformは、他のリソースの値の参照がしやすい、と個人的に感じました。
例えば先述のソースでbucket = aws_s3_bucket.b.idという記載がありますが、これだけでresource "aws_s3_bucket" "b"バケット名を参照できるので、例えばRefやFn::GetAttなどを利用しなくていいのは便利です。

また依存関係も、基本的には自動で解決してくれるので、自分で依存関係(=depends_onなど)を定義しなくてもいいのは良いなと思います。(一部、入れないといけないケースはありますが)

その他、外部リソース(アカウントIDやAWS上にあるリソースのARN等)の参照もHCLで記載できるので、わざわざローカルで定義しなくていいのは便利だと感じました。

公開モジュールを利用できる
「特徴」でも書いた通り、公開モジュールを利用することにより、わざわざモジュールやoutputの定義を自作する必要がなくなります。
そのため、公開モジュールを利用すれば、定義の手間を削減できます。

デメリット

パラメータ名が一部Cfnと異なる
一部リソースの一部パラメータ名がCfn構文のそれと異なる場合があるので、その場合はそのパラメータがどれに対応するかを調べる必要があります。(基本的には似通った名前だったり説明を読めばなんとなくわかりますが、たまに分かりにくいものもある)

有用なケース

幅広いリソースをデプロイする場合
Terraformはサポートしている範囲が広いので、数多くのリソースをデプロイする場合に向いていると感じます。(例えば、Fargateでフロント環境もして、RDBやDynamoDBなどのバックエンド環境も同時に作って...など)

別リソース値の参照や依存関係の解決がやりやすい点がその理由です。

またHCLで記載するので、特定のプログラミング言語に依存しない(=プログラミングに詳しくなくても対応できる)という点もメリットかなと思います。
(もちろん、プログラミングに詳しい場合でもTerraformを使用するメリットは大いにあります。)

個人的にはオールラウンダーなイメージで、ちょっと乱暴な言い方かもしれませんが「迷ったらTerraformを選ぶ」みたいな選択もアリなのかな?...とも思っています。

Serverless Framework

特徴

プラグインで機能拡張ができる
下記ページでプラグインが公開されており、標準で対応していないリソース・機能についても、プラグインで対応できる場合があります。

https://www.serverless.com/plugins

YAML/JSONで記載できる
Serverless Frameworkは、基本的にYAMLまたはJSON形式で記載します。
そのため、専用の言語や記法を覚える、ということはないので、その点で導入はしやすいと思います。

またnpmモジュールなので、インストールもnpm(yarn) install 1つで済むのは便利です。

メリット

とにかくLambda周りの定義が簡単
個人的に、Serverless FrameworkはとにかくLambda周りの定義が本当に簡単&充実しているのが最大のメリットだと思います。
例えば、Web APIでよくあるAPI Gateway+Lambdaの構成は、下記7行(実質6行)だけでできてしまいます。(最低限の設定であれば)

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get

またそれ以外にも、S3, DynamoDBなどのトリガ設定も充実しています。

プラグインで機能拡張できる
「特徴」にも書いた通り、プラグインで機能拡張をすることにより、標準ではサポートしていないリソースにも対応できるケースがあります。
またプラグインでは単体テストなど、リソース定義以外で役立つものもあり、デプロイ以外にも開発全般で有用なものも多いです。

デメリット

Lambda関連以外の部分の記載
逆に、Lambda関連以外のリソースについては、AWS CDKやTerraformに比べるとサポートしていないものもまだまだあります。
その場合は、resourcesセクションに純粋にCfn構文を記載することになるので、そこが煩わしい、と感じることがあると思います。

有用なケース

Lambdaを数多く使用する場合
「メリット」に書いた通り、Serverless FrameworkはとにかくLambda周りの定義が便利&シンプルなので、(Web APIなど)Lambdaをたくさん使うような環境の場合、非常に有用だと思います。

またLambda以外の部分ですが、プラグインで対応したり、場合によっては他のIaCツールと併用する...というのもありだと思います。(管理は少し煩雑化してしまうかもしれませんが...)

まとめ

以上、IaCツール3つについて、ゼロベースで導入を考えた際の個人的な見解について書きました。

ただ、いろいろあるでしょうが、あくまで上記は考え方の一つであり、最終的にはプロジェクトの特性とか、メンバーの好みで選択するのも全く問題ないと思っています。

なので、あくまで今回の内容はゼロベースで導入を考えた際の参考程度にして頂ければと思います。

それでは、今回はこの辺で。

【Terraform】基本的な構文の説明

はじめに

お久しぶりです。

ちょっと前に「やっぱりクラウドバックエンドの仕事したいなあ...」と書いていたら、偶然にもIaC(Infrastructure As Code)のタスクを担当することができました。(またすぐフロントに戻るんでしょうけど)

で、今回は(Serverless FramewrokでもAWS CDKでもなく)Terraformをやってるのですが、これはこれで面白いですね。
本当にそれぞれメリットがあり、全部使いこなせると便利だなあと思いました。

で、今回は「Terraformユーザーのバイブル」とも言われている「実践Terraform」を読んでていまいち分からなかった箇所を含め、基本的な文法の解説について書きたいと思います。

TL;DR

下記について解説

  • resource
  • variable
  • local
  • output
  • data
  • module
  • dynamic

書かないこと

  • terraformのインストール&初期設定
  • 基本的な構成(*.tfファイルに記載する、providerの設定...など)
  • 各種ファイルの説明(terraform.tfvars, terraform.tfstateなど)

前提

  • 対象providerはAWSとします。(他は知らないから)
  • variable, local, outputなどは実際は別ファイルに記載するのが推奨みたいですが、今回は同じ箇所に記載します。

resource

対象providerのリソースを作成する。

resource <固有リソース名> <識別子> 形式で記載。
<固有リソース名>は下記URL参照

https://registry.terraform.io/providers/hashicorp/aws/latest/docs

またリソースの各種プロパティは、<固有リソース名>.<識別子>.<プロパティ名> で取得可能。

# 例:S3バケットを作成する。
resource "aws_s3_bucket" "hoge" {
  bucket = "my-tf-bucket-hoge"
}  
  
# 上記S3バケットのポリシーを設定  
resource "aws_s3_bucket_policy" "hoge" {
  # 上記S3バケットのid(=バケット名)を取得する
  bucket = aws_s3_bucket.hoge.id 
  
  #policyの設定は省略 
  policy = ... 
}

variable

各リソースの各種プロパティに対して、変数を割り当てる。
変数には任意の値を設定できるので、各種プロパティに任意の値を設定できる。

設定した変数の値はvar.<変数名> 形式で取得可能。

主な用途は下記(?)

  • アプリ名・ドメイン設定など、プロジェクトで共通で使用する値の定義
    • terraform.tfvars(※)などに値を設定する
  • モジュール(後述)に設定する値の定義
    • 関数の「引数」みたいな使い方をする

※外部ファイル(*.tfvars)の中でも特殊なもので、なにも設定しなくてもこのファイルの内容から変数の値を取得してくれる。(他の外部ファイルはコマンド実行時に指定が必要)

# 例1:プロジェクトで共通で使用する値の定義  
# どこか(terraform.tfvarsと同じ階層のファイル内)にvariableを定義する  
variable "app_name" {
  type = string
}  
  
# terraform.tfvarsなどで設定する。  
app_name = hogehoge  
  
# そうすると、リソース定義などの際にそれを利用できる。  
# 文字列内で利用する場合は${var.name}とする  
resource "aws_s3_bucket" "hoge" {
  bucket = "${var.app_name}-bucket-hoge"  
  tags = {
    APP_NAME = var.app_name
  }
} 
# 例2:モジュールの引数として使用する値の定義  
# モジュール用のvariableを定義する  
variable "bucket_prefix" {
  type = string
}  
  
# モジュール側で、variableの値を参照する。
resource "aws_s3_bucket" "this" {
  bucket = "${var.bucket_prefix}-bucket-fuga"  
}  
  
# そうすると、モジュールの呼び出し側でそれを利用できる。  
# 結果として、「makky12-bucket-fuga」という名前のバケットができる  
module "aws_s3_bucket_piyo" {  
   # 上記「resource "aws_s3_bucket" "this"」を参照しているものとする
  source = "./module/s3" 
  bucket_prefix= "makky12"  
} 

local

ローカル変数。variableと似ているが、下記の点が異なる。

  • 定義した(≒同じフォルダ内の)resourceやmoduleにのみ作用する。
    • サブフォルダなどにまたがって使用することはできない。
  • 任意の値を後から設定することはできない。(宣言時の値で固定)
    • プログラム言語の定数みたいなもの。
    • ただしvariableと組み合わせることで、任意の値を初期値に設定することはできる。
      • もちろん、その値はもう変更できない

具体的な使い方は、下記サイトが参考になります。
[terraform] VariableとLocal Valueの使い所 - Qiita

# モジュールで使用する場合。
variable bucket_suffix {
    type = string
}
  
locals {  
    # localsは定義した値で固定。  
    # ただし値にvariableを使用すれば、任意の値を設定可能。  
    # (もちろん、変更は不可)
    local_prefix = "makky12"
    local_suffix = var.bucket_suffix 
}
  
resource "aws_s3_bucket" "this" {
  # localの値を参照する。
  bucket = "${local.bucket_prefix}-bucket-piyo-${local.bucket_suffix}"  
}  
  
# そうすると、リソースを呼び出す側でそれを利用できる。  
# 結果として、「makky12-bucket-fuga」という名前のバケットができる  
module "aws_s3_bucket_piyo" {  
   # 上記「resource "aws_s3_bucket" "this"」を参照しているものとする
  source = "./module/s3" 
  bucket_prefix= "makky12"  
} 

output

以下の用途に用いられる値。

  • terraform apply 時に、実際の値を出力する。
    • ただしsensitive = trueにすると、出力されない。
  • モジュールにおける、モジュールの固有のプロパティを設定できる
    • プロパティは、モジュール呼び出し側から参照できる。

取得する際は<該当モジュール名>.<output名> 形式で取得可能。

# 例1:terraform applyのoutputとして利用する  
    
# S3バケットを定義する  
resource "aws_s3_bucket" "hoge" {
  bucket = "hoge-fuga-bucket"  
}   
  
# こうすると、terraform apply時に上記S3バケットのARNが出力される
output "s3_arn" {
  value = aws_s3_bucket.hoge.arn
}   
  
# sensitive=trueの場合、値は出力されない  
output "s3_domain_name" {
  value = aws_s3_bucket.hoge.domain_name
  sensitive   = true
} 
# 例2:モジュールの引数として使用する値の定義  
# 共通モジュール側に、先程のようにS3バケットとoutputを定義する。
resource "aws_s3_bucket" "hoge" {
  bucket = "hoge-fuga-bucket"  
}   
  
output "s3_id" {
  value = aws_s3_bucket.hoge.id
}   
  
# 上記共通モジュールを呼び出す  
module "aws_s3_bucket_piyo" {  
  source = "./module/s3" 
}   
 
# 下記のようにすることで、outputの値(=S3バケットのID, =バケット名)を取得できる 
resource "aws_s3_bucket_policy" "hoge" {
  bucket = module.aws_s3_bucket_piyo.s3_id
  policy = ...
}

data

terraform外部の値を取得することができる。 例えば以下のような使い方ができる。

  • AWSアカウントから、アカウント情報(アカウントID、リージョン情報など)や既存リソースのARNを取得する
  • ファイルからデータを読み込む(externalと併用)

定義はdata <固有data名> <識別子> 形式で取得可能。(取得は下記ソース参照)

externalについては、下記サイトを参照。
- https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source#processing-json-in-shell-scripts - terraform の external data source を使って外部コマンドの実行結果を variable として使用する · GitHub

# 例1:AWSから各種情報を取得する  
    
# ACMから、発行済ドメインの証明書ARNを取得する  
data "aws_acm_certificate" "hoge" {
  domain   = "hogehoge.fugafuga.com"
  statuses = ["ISSUED"]
}
  
# terraformに紐づいているAWSアカウント情報を取得する。  
data "aws_caller_identity" "fuga" {}
  
# CloudFrontで、上記dataの情報を参照する
resource "aws_cloudfront_distribution" "hoge" {
  (一部省略)  
  tags = {
    ACCOUNT_ID = data.aws_caller_identity.fuga.account_id
  }

  viewer_certificate {
    acm_certificate_arn = data.aws_acm_certificate.hoge.arn
  }
}   
  
# sensitive=trueの場合、値は出力されない  
output "s3_domain_name" {
  value = aws_s3_bucket.hoge.domain_name
  sensitive   = true
} 
# 例2:ファイルの内容を読み込む  
# catコマンドでjsonファイルを読み込む。
  
data "external" "hoge" {
  program = ["cat", "test.json"]
}   
  
# 上記jsonファイルのキーの値を参照する。
resource "aws_s3_bucket" "hoge" {
  bucket = value = "${data.external.hoge.result["piyo"]}"
} 

module

リソース定義などの処理を共通モジュール化できる。
共通モジュール化することで、毎回同じ定義を書かずとも、最低限のvariableを共通モジュールに渡すだけで該当のリソースを何個も作成できる。

1プロジェクト内でたくさん作成するリソースの定義をモジュール化しておくと便利。(Lambdaとか)

またモジュールは自作する他に、Terraform Registryで公開されているものを使用することができる。
これを利用した場合、特に何もしなくても各モジュールが用意しているoutputを参照できる。

# 例1:自作モジュールを利用する場合  
  
# (共通)モジュール側は、通常のリソース定義と同様の書き方でOK。
variable "filename" {
  type = string
}  
  
variable "function_name" {
  type = string
}  
  
variable "handler" {
  type = string
  default = "index.handler"
} 

variable "env" {
  type = string
  default = "dev"
}  
  
resource "aws_lambda_function" "this" {
  filename      = var.filename
  function_name = var.function_name
  handler       = var.handler
  role = ... #省略
  
  runtime = "nodejs14.x"
  
  environment {
    variables = {
      ENV= var.env
    }
  }
}  
  
# 共通モジュールを呼び出す側は「module <任意のモジュール識別子名>」で  
# 共通モジュールを呼び出す。  
# sourceで共通モジュールのパス、あとはvariablesを指定する。  
# 結果として、function_hoge、function_fuga、function_piyoの
# 3つのLambda関数ができる    
module "hoge" {
  source = "./modules/lambda"
  filename = "./hoge_function.zip"
  function_name = function_hoge
}  
  
module "fuga" {
  source = "./modules/lambda"
  filename = "./fuga_function.zip"
  function_name = function_fuga
  env = "test"
}  
  
module "piyo" {
  source = "./modules/lambda"
  filename = "./piyo_function.zip"
  function_name = function_piyo
  env = "stg"
  handler = "index.main"
}
# 例2:Terraform Registryの公開モジュールを利用する場合  
  
# sourceやversionは、各モジュールページ右上の「Provision Instructions」に
# 記載してあるので、それをコピペする。  
# variable, outputも各モジュールページやリンク先のGitHubに
# 記載してあるので、それを参照する。  
# 今回は「AWS lambda」の共通モジュールを使用する。  
# https://registry.terraform.io/modules/terraform-aws-modules/lambda/aws/latest
module "lambda" {
  source  = "terraform-aws-modules/lambda/aws"
  version = "2.34.1"
  function_name = "function_hogehoge"
  description   = "hogehoge lambda function"
  handler       = "index.handler"
  runtime       = "nodejs14.x"

  source_path = "../src/hogehoge"
}
 
# 各公開モジュールで定義されているoutputは、そのまま  
# 使用することができる。 
resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket = "bucket_hogehohe"

  lambda_function {
    # ここで「lambd_function_arn(=Lambda関数のARN)」を参照
    lambda_function_arn = module.lambda.lambd_function_arn
    events              = ["s3:ObjectCreated:*"]
  }
} 

dynamic

リソース内で複数個設定可能なmap形式の同一プロパティについて、配列で指定することで配列の値を繰り返し適用できる。(string, numberなどプリミティブなプロパティにはfor_eachなどでループできる)

プログラム言語のforループ文みたいな使い方ができる。

# 例2:ファイルの内容を読み込む  
# catコマンドでjsonファイルを読み込む。
resource "aws_waf_web_acl" "hoge" {
  name        = "hogehoge"
  metric_name = "hogehoge"

  default_action {
    type = "BLOCK"
  }
  
  # 実際にrulesに設定する内容がvar.rulesに配列で渡されるとする
  dynamic "rules" {
    for_each = var.rules
    content {
      priority = rule.value["priority"]
      rule_id = rule.value["rule_id"]  
       
      # ネスト構造の場合はこうする。
      # https://www.terraform.io/language/expressions/dynamic-blocks
      dynamic "action" {
        for_each = rule.value.action
        content {
          type = action.value.type
        }
      }
    }
  }
}

まとめ

掛け足でしたが、ざっとterraformの基本構文についてまとめてました。(自分の備忘録も兼ねて)

今までTerraformは触ってませんでしたが、使ってみるとこれはこれで便利な部分も多く、面白いなあと感じました。

IaCツールは色々特徴や強み・弱みがあるので、適材適所使い分けれらるようになりたいなあ、と思います。

それでは、今回はこの辺で。

【JavaScript】数値に関する謎(?)挙動について

はじめに

前回まで、Git管理やバージョニングについて記事にしましたが、今回は一転してみんな大好き?JavaScriptの話です。

最近ちょっとSNSで話題になってた、JavaScriptの数値に関する謎?挙動についてです。

アジェンダ

  • parseInt()の挙動
  • 「+」「-」に関する挙動

parseInt()の挙動

parseInt(引数を数値に変換する関数)の挙動について、ある外国人のエンジニアの方が、こんな内容のツイートをしてました。(リンクは失念)

/*
 mysterious behavior of parseInt
*/
console.log(parseInt(0.5));  // 0
console.log(parseInt(0.05));  // 0
// (中略)
console.log(parseInt(0.000005));  // 0
console.log(parseInt(0.0000005));  // 5 ←What!?
   

上記に対して、Twitterでもいろいろツイートが飛び交ってましたね。まあ色々と。

上記の挙動について

まず、parseIntに数値を指定してるのがあまりよろしくないという点に注意です。(意味がない)

で、parseIntに数値(文字列以外)を指定した場合の挙動ですが、下記のようになります。(引数の数値をそのまま返却するわけではない点に注意)

  1. 引数をtoString()で文字列に変換する
  2. 上記文字列を数値にした値を返却する

参考:parseInt() - JavaScript | MDN

で、0.0000005をtoString()すると、下記の通り「5e-7」と指数表現された文字列が返ることが分かります。
f:id:Makky12:20220205192210p:plain

これを数値に変換するわけですが、文字列「5e-7」の「e」は数値(=10進数)に変換できないため、先頭の「5」のみ返された結果、結果が「5」となるわけです。

なお引数を文字列で指定すれば、0.0000005(やさらに小さい値)でも、正しい結果が返ります。
f:id:Makky12:20220205192828p:plain

ちなみにこの件については「1時間プログラミング」の紀平拓男さんもブログを書いてますので、そちらもぜひ。
JavaScript で parseInt / parseFloat を使わない方が良い理由

また、先述のMDNページでも下記のように「とても大きな数字やとても小さな数字を使用する際に予期しない結果を生み出すことがあります。」と記載してあります。

数値によっては e の文字を文字列表現の中で使用しますので (例えば 6.022E23 は 6.022 × 1023 を表します)、parseInt を使用して数値を切り捨てると、とても大きな数字やとても小さな数字を使用する際に予期しない結果を生み出すことがあります。parseInt を Math.floor() の代用として使うべきではありません。

基数変換でも

なお上記の文字列/数値の挙動の違いですが、8進数の基数変換の際にも起こります。
下図の通り、parseIntで8進数(っぽい値)の基数変換を行った場合、文字列or数値、基数(第二引数、以下「radix」と記載)の有無で値が変わります。

f:id:Makky12:20220206180908p:plain

まずややこしいのが、JavaScriptの8進数の扱いが文字列or数値で異なる点で、先述のMDNページの説明にもある通り、下記の挙動となります。

  • 文字列:先頭が0の値を8進数として扱わない
    • 先頭の0を除いた値を10進数文字列として扱う
  • 数値:先頭が0の値を8進数として扱う

上記により先頭2つのparseIntは、文字列「021」を文字列「21」として扱います。
そして、parseInt("21")はそのまま21、parseInt("21", 8);は「8進数の21=10進数の17」を返します。

また後ろ2つはまず数値021を10進数に変換→その値を文字列にする...が行われ、結果文字列「17」が返ります。
そして、parseInt("17")はそのまま17、parseInt("17", 8);は「8進数の17=10進数の15」を返します。

こんな感じで、parseIntを使った8進数の基数変換は結構ややこしい挙動をするので、扱いには要注意です。

16進数の場合は...

先述のMDNページの記載にもある通り、先頭が「0x(0X)」の文字列は8進数と違い、ちゃんと16進数として扱ってくれます。
なので下図の通り、parseInt("0x21")およびparseInt("0x21", 16);はどちらも33を返します。

ただ数値の場合は注意が必要で、まず0x21を10進数に変換し(=33)、それをradixで判定するので、3つめはそのまま33、最後のは「16進数の33(=0x33)=10進数の51」となり、51が返ります。

個人的には、parseIntについてはこんな感じかなあと思います。

  • 極力parseIntは使うべきではない。
    • 紀平さんのブログにもある通り、Numberを使うべき
    • 8進数の変換をする必要がある場合、ちゃんと挙動を理解して使うか、あるいは専用の関数を用意する
  • 第一引数は必ず文字列にする

f:id:Makky12:20220206182741p:plain

「+」「-」に関する挙動

また結構前ですが「+」や「-」の挙動について、別の外国人のエンジニアの方が、こんな内容のツイートをしてました。(これもリンクは失念)

/*
 mysterious behaviour of numeric-string operands
*/
console.log("2" + 2) // string "22"
console.log("22" + 2) // string "222"
console.log("222" - 2) // number 220 ←What!?
   

上記の挙動について

上記ツイートした外国人の人が言いたかったのは、

  • 「+」だと文字列なんだけど...
  • 「-」したとたん数値になってる!?

ってことなのかな?

まずJavaScriptの「+」には、

  • 文字列の場合:文字列の連結
  • 数値の場合:数値の加算

の2つの機能があります。

で、最初の2つは、左辺が文字列なので「文字列の連結」の方が実施されます。(左辺と右辺のいずれかが文字列なら、文字列連結が優先されるようです) f:id:Makky12:20220205195826p:plain

しかし「-」は「+」と違い、文字列を扱う機能はなく、単に「数値の減算」のみ行います。

最後の計算は左辺の文字列「222」が数値に変換可能な文字列なので「222 - 2」が実施され、結果220(数値)が返されます。(なお、数値に変換不可能な文字列だった場合、NaNになります)

まとめ

以上、最近SNSで話題になった、JavaScriptの謎?挙動に関する記事でした。

たしかにぱっと見「なんじゃこりゃ!?」というような挙動がJavaScriptには結構ありますが、そこでただディスるだけではなく、その仕組みを追っていくと「なるほど」とか「そういうことか」と勉強になる点も多々あります。

実際、parseInt()の挙動についてはかなり勉強になりました。(ただ、parseInt(021, 8) が15になる挙動は、理解するのに結構時間がかかりましたが...)

それでは、今回はこの辺で。