Quantcast
Browsing Latest Articles All 4 Live
Mark channel Not-Safe-For-Work? (0 votes)
Are you the publisher? or about this channel.
No ratings yet.
Articles:

これから来そうなJavaScript新機能3選

時々PHPのRFCを紹介していますが、JavaScriptにもproposalという似たような仕組みが存在します。
JavaScript最大の進歩だったasync/awaitproposal出身です。

以下はそのproposalの中から、有用そうな提案3点を取り挙げた記事、Here are three upcoming changes to JavaScript that you'll loveの日本語訳です。

Here are three upcoming changes to JavaScript that you'll love

今後来るであろうJavaScriptの便利な新機能を見てみましょう。
ここでは新しい構文の紹介、最新状況へのリンク、そして今すぐその構文を使ってみる小さなテストスイートを作成します。

How JavaScript Changes

0__ttLRUUBsYYoQS5u_.png

Ecma TC39がどのようにJavaScriptの変更を管理しているかを既に知っている場合は、このセクションは飛ばしてもかまいません。

JavaScriptの言語仕様がどのように決定しているか知らない人のために、ここでプロセスの概要を簡単に説明します。

JavaScriptは、ECMAScriptという言語標準の実装です。
この標準は、Webブラウザの進化の初期段階において、JavaScriptの独自進化を標準化するために形成されました。
ECMAScriptには8のエディションが存在しており、7回のリリースが存在します(4番目のエディションは放棄された)。

JavaScriptエンジンは、ECMAScriptがリリースされた後で、変更点の実装を開始します。
このチャートを見るとわかるように、全てのJavaScriptエンジンが全ての機能を実装しているわけではなく、一部エンジンは機能を実装するのに非常に時間がかかることもあります。
これは最善ではないと思うかもしれませんが、それでも標準が存在しない世界よりは遙かに良いと私は考えています。

Proposals

ECMAScriptの各エディションは、proposalによる審査のプロセスを経て公開されます。
proposalが有用で、かつ後方互換性を壊さないと見做された場合、それは次のエディションに追加されることになります。

proposalは5段階のステージに分かれています。
全てのproposalは、最初は"strawman"と呼ばれるステージ0から始まります。
この段階のproposalは、まだ十分に検討されていないか、もしくは、次に進む基準を満たしていないしリジェクトもされてはいないという状態です。

ステージ0のproposalは今後どうなるか不安定で、破棄されたり大幅に変更されることもあります。
従って、それらをプロダクトアプリに使ったりしないことを強く勧めます。

ここで取り上げるproposalは、いずれもステージ0ではありません。

Test Suite

プログラミングの紹介では、コードはしばしば抜粋した形で表示されます。
私はTDDが大好きなので、新機能を学ぶためにはテストを書いてみるのが一番だと思っています。

ここではKent Beckによる学習テストを使用します。
このテストで書くアサーションは、自分で書いたコードをテストするというより、言語仕様自体を確認するという意味合いが強いです。
このコンセプトは、サードパーティのAPIや他の言語を学ぶときにも役立つ方法だと思います。

Transpilers

既にトランスパイラを使っているのであれば、この章は飛ばしてもかまいません。

まだ実装されていない機能なのに、どうやって使用するのか不思議に思う人もいるかもしれません。

JavaScriptには、JavaScriptをJavaScriptに変換するトランスパイラというものがいくつも存在します。
聞いただけではとても役に立ちそうには思えない気もしますが、しかしこれが実際にはとても役立つものだと私が保証します。
ステージ0のproposalsを含んだ最新バージョンのJavaScriptを書いたとしても、トランスパイラが、今のWebブラウザやNode.jsのような実行環境で動作するようにしてくれるのです。

これは、トランスパイラが未実装のコードを古いバージョンでも動くJavaScriptに変換することによって実現されます。

現在最も普及しているJavaScriptのトランスパイラはBabelであり、以下の記事ではBabelを使用します。

Setup

以下のコードを試してみるには、Node.jsNPMがインストールされている必要があります。
トランスパイラの導入は以下のコマンドで行います。

npm init -f && npm i ava@1.0.0-beta.3 @babel/preset-env@7.0.0-beta.42 @babel/preset-stage-0@7.0.0-beta.42 @babel/register@7.0.0-beta.42 @babel/polyfill@7.0.0-beta.42 @babel/plugin-transform-runtime@7.0.0-beta.42 @babel/runtime@7.0.0-beta.42 --save-dev

次にpackage.jsonに以下を追加します。

    "scripts": {
      "test": "ava"
    },
    "ava": {
      "require": [
        "@babel/register",
        "@babel/polyfill"
      ]  
    }

最後に.babelrcファイルを作成します。

    {
      "presets": [
        ["@babel/preset-env", {
          "targets": {
            "node": "current"
          }
        }],
        "@babel/preset-stage-0"
      ],
      "plugins": [
        "@babel/plugin-transform-runtime"
      ]
    }

これで、テストを動かす準備が完了しました。

1. Optional Chaining

JavaScriptはObjectでできています。
Objectはしばしば、期待したような形になっていないことがあります。
以下は、データベースやAPIなどから取得したデータオブジェクトの例です。

    const data = {
      user: {
        address: {
          street: 'Pennsylvania Avenue',
        }, 
      },
    };

一方、データの登録を完了していないユーザがいたとしたらこうなります。

    const data = {
      user: {},
    };

何も考えずにstreetにアクセスしていた場合、以下のエラーが発生します。

    console.log(data.user.address.street); // Uncaught TypeError: Cannot read property 'street' of undefined

現状では、エラーを防ぎつつstreetプロパティを確認するには、以下のようにアクセスしなければなりません。

    const street = data && data.user && data.user.address && data.user.address.street;
    console.log(street); // undefined

私の感想としては、この方法は、
・醜悪
・厄介
・冗長
の三重苦です。

そこでOptional Chainingの登場です。

    console.log(data.user?.address?.street); // undefined

素晴らしく簡単ですね。
この機能の有用性がわかったので、もっと詳しく見ていきましょう。
さっそくテストを書いてみます。

    import test from 'ava';

    const valid = {
      user: {
        address: {
          street: 'main street',
        },
      },
    };

    function getAddress(data) {
      return data?.user?.address?.street;
    }

    test('存在すれば値を返す', (t) => {
      const result = getAddress(valid);
      t.is(result, 'main street');
    });

Optional Chainingは、?のつかないドット記法と互換を保っていることがわかります。
次に、存在しないプロパティを参照するテストを追加しましょう。

    test('プロパティがなければundefinedが返ってくる', (t) => {
      t.is(getAddress(), undefined);
      t.is(getAddress(null), undefined);
      t.is(getAddress({}), undefined);
    });

配列プロパティに対してのOptional Chainingの動作は以下のとおりです。

    const valid = {
      user: {
        address: {
          street: 'main street',
          neighbors: [
            'john doe',
            'jane doe',
          ],
        },
      },
    };

    function getNeighbor(data, number) {
      return data?.user?.address?.neighbors?.[number];
    }

    test('Optional Chainingは配列プロパティも対象', (t) => {
      t.is(getNeighbor(valid, 0), 'john doe');
    });

    test('配列プロパティがなければundefinedが返ってくる', (t) => {
      t.is(getNeighbor({}, 0), undefined);
    });

Object内に関数が実装されているかどうかわからないことがあります。
よくある例はWebブラウザです。
古いブラウザには特定の機能が存在しない場合があります。
幸いなことに、関数の実装有無についてもOptional Chainingで検出可能です。

    const data = {
      user: {
        address: {
          street: 'main street',
          neighbors: [
            'john doe',
            'jane doe',
          ],
        },
        getNeighbors() {
          return data.user.address.neighbors;
        }
      },
    };

    function getNeighbors(data) {
      return data?.user?.getNeighbors?.();
    }

    test('Optional Chainingはfunctionも対象', (t) => {
      const neighbors = getNeighbors(data);
      t.is(neighbors.length, 2);
      t.is(neighbors[0], 'john doe');
    });

    test('関数がなければundefinedが返ってくる', (t) => {
      const neighbors = getNeighbors({});
      t.is(neighbors, undefined);
    });

Optional Chainingで値を取得できなかった場合、そこで処理が止まります。
動作のイメージとしては概ね以下のようなかんじになります。

    value == null ? value[some expression here]: undefined;

従って、オペレータ?がundefinedかnullを返した場合、それ以降の処理は実行されません。
以下のテストでその動作を確認できます。

    let neighborCount = 0;

    function getNextNeighbor(neighbors) {
      return neighbors?.[++neighborCount];
    }

    test('++neighborCountは一回しか呼ばれてない', (t) => {
      const neighbors = getNeighbors(data);
      t.is(getNextNeighbor(neighbors), 'jane doe');
      t.is(getNextNeighbor(undefined), undefined);
      t.is(neighborCount, 1);
    });

貴方は既にこの機能を使うことができます。
Optional Chainingを使うことで、余計なif文、lodashのようなライブラリ、&&を使わずに済むようになります。

A word of warning

もしかしたら、Optional Chainingに多少のオーバーヘッドがあることに気付いたかもしれません。
?を過度に使用しすぎると、パフォーマンスが低下します。

Objectを作成したときか、受け取ったときにだけOptional Chainingを使って検証を行うことを勧めます。
それによって以後のチェックの必要がなくなり、パフォーマンス低下を抑えられます。

Link

proposalはここにあります。
この記事の一番下にもリンクの一覧を置いておきます。

2. Nullish coalescing

JavaScriptでよく見られる処理に以下のようなものがあります。
・値がundefinedかnullでないかをチェックする
・チェックに引っかかればデフォルト値を入れる
・ただし0false、''などfalsyな値はそのまま通す

以下のようなコードになるでしょう。

    value != null ? value : 'default value';

もしくは以下のように不適切な処理を見たことがあるかもしれません。

    value || 'default value'

こう書いてしまうと、0false、''なども全てfalseと見做されてしまい、デフォルト値が入ってしまいます。
そのため、値は明示的にnullと比較しなければなりません。

    value != null

これは以下と同じです。

    value !== null && value !== undefined

ここで新たなオペレータ、Nullish coalescingの登場です。

    value ?? 'default value';

これは、falsyな値をうっかりデフォルト値で上書きすることを防ぎつつ、三項演算子と!= nullを使わずにnullとundefinedをチェックすることができます。
構文がわかったので、実際にどう動くかテストを書いてみます。

    import test from 'ava';

    test('nullは該当', (t) => {
      t.is(null ?? 'default', 'default');
    });

    test('undefinedも該当', (t) => {
      t.is(undefined ?? 'default', 'default');
    });

    test('void 0も該当', (t) => {
      t.is(void 0 ?? 'default', 'default');
    });

    test('0はNullish coalescingではない', (t) => {
      t.is(0 ?? 'default', 0);
    });

    test('空文字もNullish coalescingではない', (t) => {
      t.is('' ?? 'default', '');
    });

    test('falseもNullish coalescingではない', (t) => {
      t.is(false ?? 'default', false);
    });

null、undefined、およびvoid 0がNullish coalescingにひっかかることがわかります。
逆に0や''、falseなどfalsyな値では引っかかりません。
なおvoid 0はundefinedと評価されます。

proposalはこちらです。

3. Pipeline operator

関数型プログラミングには複数の関数を合成するCompositionという機能があります。
各関数は手前の関数の出力を入力として受け取ります。
プレーンなJavaScriptでの例を以下に示します。

    function doubleSay (str) {
      return str + ", " + str;
    }
    function capitalize (str) {
      return str[0].toUpperCase() + str.substring(1);
    }
    function exclaim (str) {
      return str + '!';
    }
    let result = exclaim(capitalize(doubleSay("hello")));
    result //=> "Hello, hello!"

これらの書き方はとても一般的なため、lodashramdaをはじめ多くのライブラリでサポートされています。

Pipeline operatorを使うと、サードパーティライブラリを使わず以下のように書けるようになります。

    let result = "hello"
      |> doubleSay
      |> capitalize
      |> exclaim;

    result //=> "Hello, hello!"

これの目的は、関数のチェーンをより読みやすくするためです。

将来は部分適用もできるようになる予定ですが、現在は以下のように確認できます。

    let result = 1
      |> (_ => Math.max(0, _));

    result //=> 1
    let result = -5
      |> (_ => Math.max(0, _));

    result //=> 0

シンタックスがわかったのでテストを書いてみます。

    import test from 'ava';

    function doubleSay (str) {
      return str + ", " + str;
    }

    function capitalize (str) {
      return str[0].toUpperCase() + str.substring(1);
    }

    function exclaim (str) {
      return str + '!';
    }

    test('Simple pipeline usage', (t) => {
      let result = "hello"
        |> doubleSay
        |> capitalize
        |> exclaim;

      t.is(result, 'Hello, hello!');
    });

    test('Partial application pipeline', (t) => {
      let result = -5
        |> (_ => Math.max(0, _));

      t.is(result, 0);
    });

    test('Async pipeline', async (t) => {
      const asyncAdd = (number) => Promise.resolve(number + 5);
      const subtractOne = (num1) => num1 - 1;
      const result = 10
        |> asyncAdd
        |> (async (num) => subtractOne(await num));

      t.is(await result, 14);
    });

ひとつ注意点としては、Pipeline operatorで非同期関数を使う場合はawaitしなければならないことです。
そうしないと、値がPromiseになってしまうからです。
非同期関数をうまく扱う|> awaitのような文法がいくつか提案されていますが、いずれも未実装か未決定です。

さて、ここまで読んできたことで、これらの機能を使いこなすことができるようになったことでしょう。
これらの機能を使って快適なプログラミングができることを信じています。

The full code

全てのテストコードはこちらで見付けることができます。

TC39/proposal-optional-chaining
TC39/proposal-nullish-coalescing
TC39/proposal-partial-application
TC39/proposal-pipeline-operator

感想

PHPの@叩かなかった人だけがOptional Chainingを賞賛するがよい。

今回は英文がかなりわからなかった。
「If you want to follow along with the code then feel free.」わからん。
「Each level that you check with ? must be wrapped in some sort of conditional logic under the hood. 」わからん。

JavaScriptのpipeline operatorについてまとめてみた

Rubyにpipeline operator(以下pipeline演算子)が導入されると話題になっているようです(【速報】Ruby の trunk に pipeline operator がはいった, Twitterでの検索結果、など)。

残念ながら私はRubyがほとんどわからないので、代わりにJavaScript(この場合ECMAScriptと呼ぶべきですが)に提案されているpipeline演算子についてまとめてみようと思います。

pipeline演算子の現状について

JSのpipeline演算子はまだ正式に導入されたわけではなく、いわゆるStage 1と呼ばれる状態で、まだ仕様自体しっかり固まっていない状態です(Stageについてはこちらがわかりやすいです)。
そのため、将来的に本記事の内容とは違う文法・仕様になっている可能性もあるため、ご注意ください。

更に、pipeline演算子のproposalにも書かれているのですが、仕様にF# PipelinesSmart Pipelinesと呼ばれる2つの案があるようです(違いは後述)。

2つの仕様はOriginal / Minimal Proposal(以下Minimal proposal)と呼ばれるベースの仕様を発展させる形で提案されています。
そのため、まずはMinimal proposalについて解説を行います。

Minimal proposalについて

Minimal proposalでは、pipeline演算子の基本的な仕様が提案されています。
それではpipeline演算子について解説していきます。

pipeline演算子は|>という記号を用いることで左辺の値を引数として右辺の関数を呼び出すことができるというものです。
つまり、以下の2つの式は同じ意味になります。

x |> f
f(x)

これだけでは何が嬉しいのかわからないと思いますが、lodashなどを用いて「関数の結果を別の関数に引数として渡すのを繰り返す」ときに可読性が高まります。

例えば、「配列のそれぞれの値を2乗し、値が10以上のものをソートして取り出す」という処理はpipeline演算子無しだと以下のように書けると思います。

_.sortBy(_.filter(_.map(arr, n => n * n), n => n >= 10))

上記のコードの問題点として、行いたい処理の順番(2乗する→10以上のみ取り出す→ソートする)と、コード上の記述順序(_.sortBy, _.filter, _.map)が逆というものがあり、コードを読む際の認知負荷が高くなっています。

pipeline演算子を使うことで、上記の処理を以下のように書くことができます。

arr
|> (arr => _.map(arr, n => n * n))
|> (arr => _.filter(arr, n => n >= 10))
|> _.sortBy

ご覧の通り、行いたい処理の順番とコード上の記述順序が一致しています。

アロー関数を用いているのは、Minimal proposalではpipeline演算子の右辺に記述する関数のどこに左辺の値(arr)を渡すのか指定することができないためです(Smart Pipelinesだと可能)。
そのため、第一引数にarrを渡せば良いだけの_.sortByではアロー関数を用いていませんし、例えば「2乗する」「10以上のみ取り出す」を関数化してあれば以下のように書けます。

const pow2 = arr => _.map(arr, n => n * n);
const filterGreaterThan10 = arr => _.filter(arr, n => n >= 10)

arr
|> pow2
|> filterGreaterThan10
|> _.sortBy

Minimal proposalの問題点

Minimal proposalには以下の2つの問題点があります。

  1. アロー関数にカッコが必須
  2. awaitが使えない

アロー関数にカッコが必要

先程も記載したコード例を改めて提示します。

arr
|> (arr => _.map(arr, n => n * n))
|> (arr => _.filter(arr, n => n >= 10))
|> _.sortBy

(arr => _.map(arr, n => n * n))のように、アロー関数がカッコで囲われています。
これは、文法上の曖昧さが残ってしまうためカッコなしをエラーにする仕様となっているようです。

例えば、

a |> b => c |> b

と書いた際に、

a |> b => (c |> b) // (b => b(c))(a)
a |> (b => c) |> b // b((b => c)(a))

のどちらと解釈されるのかがわかりづらいということです。

そのため、カッコのないアロー関数は構文エラーとする、というのがMinimal proposalでの仕様のようです。

awaitが使いづらい

awaitが使いづらいとはどういうことかというと、以下のコードがエラーになるということです。

async function f(n) {
  return n
    |> generateUrlWithQuery(n) // 引数nを元に、クエリ付きURLを生成する関数のイメージ
    |> await fetch // エラー!!
}

こちらもアロー関数のときと同じように曖昧さによるものだと説明されており、n |> await fというコードが(await f)(n)await f(n)のどちらの意味なのかがわかりづらいため、仕様としてエラーになるようになっているのです。

逆にいうと、曖昧さのない構文なら問題ないため、n |> (await f)n |> (async n => f(n))のように書けばエラーではなくなります。

F# Pipelinesについて

F# Pipelinesでは、前述のMinimal proposalの2つの問題点を以下のように解決するようです。

  1. アロー関数にカッコを付けなかったときの扱いを定義
  2. pipeline演算子の右辺にawaitのみの記述可能にする

アロー関数にカッコを付けなかったときの扱いを定義

pipeline演算子の右側でアロー関数を定義した際に、後続のpipeline演算子はアロー関数のボディに含めないように定義してしまう、というものです。

先程の

a |> b => c |> b

という例は、

a |> (b => c) |> b

と解釈される、と決めてしまうということです。
もしa |> b => (c |> b)と解釈させたい場合は、そのようにカッコつきで書いてあげれば良いです。

pipeline演算子の右辺にawaitのみの記述可能にする

以下のように、awaitのみを書くことで、左辺のPromiseが解決するのを待つという案のようです。

async function f(n) {
  return n
    |> generateUrlWithQuery(n) // 引数nを元に、クエリ付きURLを生成する関数のイメージ
    |> fetch
    |> await // ここで上段のfetchが解決されるのを待つ
}

これによって、n |> (async n => fetch(n))という若干面倒な記述を用いる必要がなくなります。

以上が、F# Pipelinesの説明です。

Smart Pipelines

Smart Pipelinesはpipeline演算子の右辺をTopic styleBare styleに分類し、Topic styleでは#記号を使って式の中の任意の位置に「左辺の値」を埋め込めるようにすることができます。

言葉だけだとよくわからないと思うので、コード例を見てみましょう。

Bare styleの例

識別子と.のみの式はBare styleとして扱われ、Minimal proposalのpipeline演算子と同じことができます。
ただし、式に( )[ ]なども含めることはできません。

v |> o.fn // o.fn(v) と同じ
v |> f // f(v) と同じ
v |> console.log // console.log(v)と同じ
v |> obj[prop] // エラー(ただし、エラーの内容はTopic styleに関連するエラーです)

Bare style以外の全ての式はTopic styleとして扱われます。

Topic styleの例

Topic styleの右辺には、任意の式を書くことができ、式の中の#というトークンは左辺の値に置き換えられます。
また、必ず#を1つ以上含める必要があり、無かった場合はエラーになります(Bare styleの例にあったエラーはこのエラーです)。

v |> # + 1 // v + 1 と同じ
v |> # / (1 + #) // v / (1 + v) と同じ
v |> obj[prop] // エラー #を使っていない
v |> obj[prop](#) // obj[prop](v) と同じ

arr
|> _.map(#, n => n * n) // _.map(arr, n => n * n)
|> _.filter(#, n => n >= 10) // _.filter(中略, n => n >= 10)
|> _.sortBy // (Bare style) _.sortBy(中略)

Smart Pipelinesでは、pipeline演算子の右辺部分にアロー関数を使うことは(おそらく)無いため、アロー関数にカッコが必須という制約も問題なくなります。

また、awaitが出てくる例ですが、n |> await fTopic styleにも関わらず#が使われていないためエラーとなり、n |> await f(#)n |> (await f)(#)と書くことができるようになります。

式と変換後の対応表

ここを参考に、pipeline演算子を用いた式がBare styleTopic styleのどちらに分類されるのか、どのような式に変換されるのかをまとめてみました。

style 変換後
value |> f Bare style f(value)
value |> x + 1 Topic style Syntax Error
value |> # + 1 Topic style value + 1
value |> o.m Bare style o.m(value)
value |> o.m(1, 2) Topic style Syntax Error
value |> o.m(#, 1, 2) Topic style o.m(value, 1, 2)
value |> o.m(1, 2, #) Topic style o.m(1, 2, value)
value |> o.m(1, 2)(#) Topic style o.m(1, 2)(value)
value |> await o.m(#) Topic style await o.m(value)

以上が、Smart Pipelinesの説明です。

pipeline演算子を試してみるには

F# Pipelinesは現在実装中のようですが、Minimal proposalSmart PipelinesについてはBabelを用いてトランスパイルすることが可能なようです(@babel/plugin-proposal-pipeline-operator · Babel)(Try it out)。

所感

Smart Pipelines#を使った独特な記法は最初は気持ち悪いかもしれませんが、この記事を書くために眺めていたら慣れました()

普段JSしか書いてないのでpipeline演算子がある言語を使ったことがなく旨味がまだちゃんとわかっていませんが、関数の結果を他の関数に渡すことはそれなりの頻度であるので、正式採用されれば便利なんだろうなぁとほんのりと感じました。
ただメソッドチェーンと変わらないpipelineはどこが便利なのか正直わかりません。

参考リンク

JavaScriptのエキサイティングな新機能7選

以下はMostafa Gaafarによる記事、7 New Exciting JavaScript Features You Need to Knowの日本語訳です。

7 New Exciting JavaScript Features You Need to Know

JavaScript ( ECMA Script ) は進化する言語であり、たくさんのproposalやアイデアが出番を待ち受けています。
TC39 (Technical Committee 39) という委員会がJavaScript標準と新機能の定義を担当しています。
そして今年は彼らの活動が活発になっています。
以下は、現在ステージ3にある提案の一部の紹介です。
ステージ3は完成する直前の段階です。
これはつまり、この機能がブラウザやその他のJavaScriptエンジンにすぐに実装されることを表しています。
実際、以下のいくつかは既にブラウザに実装されています。

1. Private fields

ChromeとNodeJS12に実装済。

はい、そうです。
JavaScriptはようやくprivateを手に入れました。
もうprivateプロパティを作るためにthis._doPrivateStuff()したりクロージャに閉じ込めたりWeakMapを使ったりといった小細工を弄する必要はありません。

01.jpg

構文は以下のようになります。

// private修飾子は`#`
class Counter {
  #x = 0;

  #increment() {
    this.#x++;
  }

  onClick() {
    this.#increment();
  }

}

const c = new Counter();
c.onClick(); // OK
c.#increment(); // エラー

Proposal:https://github.com/tc39/proposal-class-fields

2. Optional Chaining ?.

オブジェクト内のネストの奥深くにあるプロパティにアクセスしようとして、悪名高いCannot read property 'stop' of undefinedエラーに遭遇した人は多いでしょう。
そうなったら次は、チェーン内の未定義オブジェクトに対応できるようにコードを変更します。

const stop = please && please.make && please.make.it && please.make.it.stop;

// あるいは'object-path'みたいなライブラリを使う
const stop = objectPath.get(please, "make.it.stop");

Optional Chainingがやってくると、同じことを簡単に書けるようになります。

const stop = please?.make?.it?.stop;

Proposal:https://github.com/tc39/proposal-optional-chaining

3. Nullish Coalescing ??

オプション値が存在しない場合にデフォルト値を使用するという実装はとても一般的です。

const duration = input.duration || 500;

この書き方の問題点は、||を使うと、有効な入力値である可能性のある0や''、falseといったfalseっぽい値が全て500になってしまうことです。

そんなわけでundefinedおよびnullのみを識別するNull合体演算子を新設します。

const duration = input.duration ?? 500;

Proposal:https://github.com/tc39/proposal-nullish-coalescing

4. BigInt 1n

ChromeとNodeJS12に実装済。

JavaScriptの数値演算がひどいものだった理由のひとつは、2^53を超える数値を扱うことができないということです。
幸いなことに、BigIntがこの問題を解決します。

02.jpg

理屈抜きで使い方を示すとこう。

// 数値リテラルにnを付けるとBigIntになる
const theBiggestInt = 9007199254740991n;

// constructorでもよい
const alsoHuge = BigInt(9007199254740991);

// constructorには文字列も渡せる
const hugeButString = BigInt('9007199254740991');

BigIntには通常の数値と同じ演算子、+-/*%などを使うことができます。
ただし、ほとんどの操作ではBigIntと通常の数値リテラルを混ぜることはできません。
BigIntとNumberを比較することはできますが、加算はできません。

1n < 2 // true

1n + 2 // Uncaught TypeError: Cannot mix BigInt and other types

Proposal:https://github.com/tc39/proposal-bigint

5. static Fields

ChromeとNodeJS12に実装済。

これはとても簡単です。
多くのOOP言語同様、クラスにstaticフィールドを設定できます。
これは列挙型の代替にも使用可能で、privateとも共存可能です。

class Colors {
  // public static
  static red = '#ff0000';
  static green = '#00ff00';

  // private static
  static #secretColor = '#f0f0f0';
}

font.color = Colors.red;
font.color = Colors.#secretColor; // Error

Proposal:https://github.com/tc39/proposal-static-class-features

6. Top Level await

Chromeに実装済。

コードのトップレベルでawaitを使用できます。
いちいち非同期関数でラップせずに済むようになるので、コンソール上でfetchのような非同期処理をデバッグするときなど非常に便利です。

03.jpg

Async/Awaitの復習が必要なら、私の過去記事を参照ください。

もうひとつの便利な使用例は、非同期で初期化が行われるESモジュールをトップレベルで使えるようになることです。
たとえば接続を確立する必要があるデータベースなどです。
このような非同期モジュールをawaitでインポートすると、そのモジュールは初期化が終わるまで待機し、その後そのモジュールに依存する次のモジュールを実行します。
これによって、返ってきたPromiseが解決されるまで待つという現在のスタンダードよりも、ずっと簡潔に非同期を処理することができます。
モジュールは、依存先モジュールが非同期なのか同期なのかを知る必要はありません。

db.mjs
export const connection = await createConnection();
server.mjs
import { connection } from './db.mjs';
server.start();

この例では、db.mjsの処理が完了するまでserver.mjsは待機します。

Proposal:https://github.com/tc39/proposal-top-level-await

7. WeakRef

ChromeとNodeJS12に実装済。

弱い参照は、いつまでも存続しているとはかぎらない参照です。

const、let、varで生成した変数は、その変数がアクセス可能であるかぎり決してメモリから削除されることはありません。
これらは全て強い参照です。
弱い参照で生成されたオブジェクトは、GCによって削除される可能性があります。
WeakRefインスタンスには、参照された元オブジェクトを返すメソッドderefがあります。
元オブジェクトが削除されていたらundefinedになります。

これは、メモリにずっと保存しておきたい程ではない重要性の低いオブジェクトをキャッシュしておきたいときに役立ちます。

const cache = new Map();

const setValue =  (key, obj) => {
  cache.set(key, new WeakRef(obj));
};

const getValue = (key) => {
  const ref = cache.get(key);
  if (ref) {
    return ref.deref();
  }
};

// キャッシュにあればそれを返す、なければ再計算する
const fibonacciCached = (number) => {
  const cached = getValue(number);
  if (cached) return cached;
  const sum = calculateFibonacci(number);
  setValue(number, sum);
  return sum;
};

不規則に削除される可能性があるため、リモートデータなどをキャッシュするために使うのは良い考えではありません。
それらにはLRUキャッシュなどを使った方がよいでしょう。

Proposal:https://github.com/tc39/proposal-weakrefs

これらのクールな新機能を使ってみて、私と同じように興奮してください。
また、ここで取り上げなかった他のプロポーザルもTC39のGitHubに公開されています。

コメント欄

「privateの#は頭おかしい」「完全に同意」「唖然とするわ」「privateキーワード追加しろよ」「いやまあ、色々と事情があるんだよ、きっと」
「Null合体演算子が楽しみ。Optional ChainingはKyle Simpsonから聞いただけだけど便利そう。」
「BigInt大歓迎。」
「弱参照知らなかった。うまく使えばWebアプリのパフォーマンスを大幅上昇できるかも。」
「TS使ってる身としてはそう目新しいものでもない。だが良いことだ。」
「Chromeで使えるのいいね。」

感想

初見のときから思ってたんだけど、privateアクセス修飾子の#はあまりに気持ち悪いですね。
staticはキーワードとして導入したのに、どうしてprivateは入れなかったのでしょうか。

もちろん理由はproposalに書かれてるのですが、主な理由としては『privateフィールドが存在していることすら外部に出したくない』だそうです。
いや、そこまでする必要あるのか?
PHPどころかJavaですら『privateフィールドが存在する』こと自体は外部からわかるぞ。

そんなわけでprivateフィールド#aのあるクラスに外からa=1とすると、privateフィールド#aとは別のpublicフィールドaが生えます。

class A{
  #a = 0;
}

a = new A();
a.a = 1; // エラー出ない

privateフィールドというより、外部と完全に切り離された内部変数みたいなかんじですね。
まあ、このあたりの仕様や文法を決めるにあたっては何年もの議論があったらしいので、後からちょっと覗いた程度では窺い知れぬ何か深い事情があるのでしょう。きっと。

結果としてJavaScriptのprivateフィールドは、リフレクションのような手段をもってしてもアクセスできない、それどころか存在するかどうかすら検出できないという、非常に本気のステルス性を手に入れました。
でもChromeのコンソールとかでは普通に見えるんですけどね。

BigIntはNumberと混ぜられないのが面倒ですね。
GMPを見習ってどうぞ。

PHP
$n = gmp_init('9007199254740991');
var_dump($n+1); // GMP(9007199254740992)

PHPの場合、GMPオブジェクトに数値演算すると結果もGMPオブジェクトとなり、透過的に計算できるので便利です。
どうしてこうしなかったのでしょうか?

とまあ少々疑問に感じる点もないでもないものの、いずれの機能も、導入されれば役に立つであろうことは間違いないはずです。
また、proposalでは、他にも多くの新機能や構文が自分の登場する順番を待っています。
色々見てみると楽しいかもしれません。

個人的には追加よりむしろ末尾;補完を削除してほしいのですが、これはどう考えても無理だろうな。

JavaScriptにもrangeが来るかもって話です

注: Stage 1なので、この記事のとおり、実装される保証も、そもそも実装される保証もありません。提案されているってだけの話です。

https://github.com/tc39/proposal-Number.range

というProposalでJavaScriptにNumber.rangeとBigInt.rangeを追加しようという提案がされています。
JavaScriptで連番の配列を作成する関数そのものは存在せず、間接的に生成するしかありません。
関数本来の使い方でないうえ、0からの連番でない場合は少し面倒です。

[...Array(5).keys()];//[0, 1, 2, 3, 4]Array.from({length:4},(_,i)=>i+1);//[1, 2, 3, 4]

いろいろなライブラリにて、そのような関数は用意されています。
ですが、これを標準で用意しようというProposalです。
上記Proposalから引用します。
現状だと、Number、BigIntそれぞれに個別の関数が追加されいます。

for(constiofBigInt.range(0n,43n))console.log(i)// 0n to 42n// With iterator helper proposalNumber.range(0,Infinity).take(1000).filter((x)=>!(x%3)).toArray()function*even(){for(constiofNumber.range(0,Infinity))if(i%2===0)yieldi};[...Number.range(1,100,2)]// odd number from 1 to 99

上記の通り、配列でなくイテレーターを返します。
ですので、配列を生成する関数ではないようです。まぁ、 スプレッド構文を利用することで簡単に配列に変換できます。
また、Iterator HelpersのProposalを利用することで、filterやmapなでの関数も利用できます。

https://github.com/tc39/proposal-iterator-helpers

範囲ですが、fromを含み、toは含まないという範囲のようです。

[...Number.range(0,10)]//[0, 2, 3, 4, 5, 6, 7, 8, 9, 9][...Number.range(0,10,2)]//[0, 2, 4, 6, 8][...Number.range(0,11,2)]//[0, 4, 6, 8, 10]

無限イテレーター

この提案がイテレーターを返す利点として、無限イテレーターがあります。

for(constnofNumber.range(0,Infinity)){if(n*n>26)break;console.log(n);}//0//1//2//3//4//5

上記のコードのように、事前に列挙の終端が分からない場合に便利です。

詳細

interfaceNumberConstructor{range(from:number,to:number,step?:number):Iterator<number>// If accept Number.range(to)range(to:number):Iterator<number>}interfaceBigIntConstructor{range(from:BigInt,to:BigInt,step?:BigInt):Iterator<BigInt>// If accept BigInt.range(to)range(to:BigInt):Iterator<BigInt>}

1番目の引数 from

列挙の開始の数字です。この数字は含まれます。
また、無限の値はエラーになります。

2番目に引数 to

列挙の数字の表示です。この数字は含まれません。
こちらは無限も許可されます。
1番目の引数>2番目の引数の場合でもstepが-1になることはなく、直後に終了するイテレーターになるようです。
また議論中のようですが、2番目以降の引数を省略した場合、1番目の引数が終端の数字となり、始点が0になるようです。

3番目の引数 step

1回ごとに加算される値です。省略した場合は1になります。
また、stepというプロパティをもつオブジェクトを渡した場合、そのstepプロパティの値が使われるようです。
また、無限の値もしくは0のときはエラーになります。

追加構文

関数だけでなく、構文でも連番が作れるようになるかもしれません。

https://github.com/oauo/Range-Proposal

[1..5];// [1,2,3,4,5][6..3];// [6,5,4,3][..4];// [0,1,2,3,4][2..];//[2,1,0][1..2..5];// [1,3,5][1..2..6];// [1,3,5][5..2..1];// [5,3,1][6..2..1];// [6,4,2][..2..5];// [0,2,4][..2..6];// [0,2,4,6][5..2..];// [5,3,1][6..2..];// [6,4,2,0]

多言語でよくある「..」で範囲をつくるやつですね。
こちらは始点、終点ともに含まれるようです。また、無限イテレーターは対応されていないようです。
また、始点、終点ともに省略した場合は0にあるようです。
始点>終点の場合は負の報告に列挙されるようです。
途中に数字を挟むことで、その数字が2番目の値になるような列挙がされるようです。
こちらは別のリポジトリで提案されており、上記のリポジトリのissueにても提案されています。
そのため、上記の提案と仕様に違いがあるようです。

Browsing Latest Articles All 4 Live