😸

人間中身が重要なんていうけれど。Ts、君の中身を見せてくれ

に公開

誰と結婚したい?
誰と付き合いたい?
なんて話になると、私のネガティブ根性がここぞとばかりに顔を出し。
「僕みたいな人には選ぶ権利ないよ。顔もかっこよくないし」

すると、鬼の首を取ったかのように、
「いや、結局中身よ。それこそ30代とかになると顔じゃなくて人柄が重要になるから大丈夫」

いや、僕まだ全然20代
いや、これ遠回しに悪口?
いや、自分で卑下してるんだからそれを相手に受容されるのは当たり前か
いや、そもそもノリ悪くネガティブ発言したのが原因ですねこれ
いや、ごめんなさい

なんてことを考えてるとふと思いました。
TypeScriptってJsに型というエッセンスを入れた言語だよね?
C言語とは違い一回Jsになってから機械語に代わるんだよね?
Tsお前、型を前面に押し出し我々に媚びを売り、中身のJsコードは悪さし放題してるんじゃないだろうな?

という事で、TypeScriptコードがJsにコンパイルされた際にどのような処理に変換されるのか見ていきましょう

前提

基本的にはnpx tscを使ってTsコードをJsコードに変換してそれを比較してワーキャー言うだけです
Jsコード側のイメージができるようになればバンドルサイズやパフォーマンスの観点で有利に働くこともあるので、そういったことに興味がある方はお付き合いをいただけると幸いです(ぺこり)

また、TargetにはES2024を使用しています。

例題

以下のようにまず、Tsファイルを示し

const messages = "Hello world"
console.log("Hello world")

次にJsファイルを示し

"use strict";
const messages = "Hello world";
console.log("Hello world");

そして、結果についてワーキャー言います。
特別記載がなければすべてnpx tscでコンパイルして比較します

Enum

JSにEnumはないのでどうなるのか

.ts
enum Book {
  nand2Tetoris,
  Hackers,
  Inoveters,
  Monkey,
}

そしてJSは...ワクワク

.js
"use strict";
var Book;
(function (Book) {
    Book[Book["nand2Tetoris"] = 0] = "nand2Tetoris";
    Book[Book["Hackers"] = 1] = "Hackers";
    Book[Book["Inoveters"] = 2] = "Inoveters";
    Book[Book["Monkey"] = 3] = "Monkey";
})(Book || (Book = {}));

1行目は厳格モードなのでEnumは関係なし
それ以降を見てみましょうか

2行目は変数の宣言だけして値は入れず。
そのあとにくるのは即時実行関数(IIFE)ですね
ものすごく誤解を招く言い方をすると、自分で自分を実行することができる関数です
本来は関数は定義したら、他のどこかで改めて関数を呼び出すことで処理が実施されます
しかし、IIFEは別途呼び出す必要がなく関数自身が自分を呼び出し処理を実行することができます

この式を見ると、

(Book || (Book = {}));

この部分が引数として渡されますが、
最初に宣言したBook変数がUndefinedであれば、{}を代入して入れて引数に渡す。
値が入っていればそのままそれを引数として渡すという事ですね

    Book[Book["nand2Tetoris"] = 0] = "nand2Tetoris";
    Book[Book["Hackers"] = 1] = "Hackers";
    Book[Book["Inoveters"] = 2] = "Inoveters";
    Book[Book["Monkey"] = 3] = "Monkey";

そして重要な中身~
基本的には同じ処理を値を変えて行ってるだけなので、1行目に注目して

Book[Book["nand2Tetoris"] = 0] = "nand2Tetoris";

評価の順番として、まず「Book["nand2Tetoris"] = 0」が実行されます
つまり、Bookオブジェクトにnand2Tetorisプロパティを追加してその値に0を入れるという事です。

Book: {
  nand2Tetoris: 0
}

そして、先ほどの式の結果は「0」になります。
Jsの場合、代入式の結果は右側の値を返却することになっているため、今回でいうと0ですね
つまり、先ほどの処理が終わると以下のように式が変換されます

Book[0] = "nand2Tetoris";

これは先ほどとプロパティ名と値が反対になっただけですね。
つまり、最終的には以下がBookに格納されます

Book: {
  nand2Tetoris: 0,
  '0': 'nand2Tetoris'
}

あとは、これを繰り返しているだけなので関数処理が完了すると

{
  '0': 'nand2Tetoris',
  '1': 'Hackers',
  '2': 'Inoveters',
  '3': 'Monkey',
  nand2Tetoris: 0,
  Hackers: 1,
  Inoveters: 2,
  Monkey: 3
}

となります!これがJsでのEnumの正体です!!!!!
やったー!!!
なんかこういう裏側を知れるのって嬉しい気持ちになりますよね!!

おそらくTypeScriptでEnumを使用している方は自然とKeyを使ってEnumを操作したり、
Valueを使って操作したりすると思いますが、そういった機能は相互参照可能なオブジェクトを作成しているからこそできること。という事ですね!

Enumについて別の書き方やマージしたときの挙動も試していますので、
お時間ある方はこの記事の「いろいろ試してみた!」以降を読んでいただけると嬉しいです!
気になりますよね?よね!EnumのプロパティへのアクセスをしたらJsはどういった処理になるのか...
おいで~、見せてあげるよ~。悪い人じゃないよ~

varの使用

※少し気になったので
2行目は変数の宣言だけして値は入れずですね
enumには再代入できるのでconstじゃないと思いましたがletじゃないのか。。

という事で、調べてました。
いくつか理由はありまして、

  • 互換性担保のため

TsはJs ES3/ES5といった古いバージョンとの互換性を担保しています
ES6で追加されたconstやletを使用すると互換性が崩れる
そのため、変換した際はvarを使用するようになっているとのこと。

てことは、これ以降Js処理でconstやletって出てこない?

  • varの柔軟性

先ほどの互換性がクリティカルな原因だと予想していますが、
varを使うことを正当化するための理由としてvarの特性があるかもしれません

varは関数スコープだったり、再代入、再宣言も可能です。
しかもTDZも防がれただ動かすという観点においてvarほど便利なものはありません

仮にvarを採用しない場合、再代入や再宣言、TDZのチェックなどをコンパイルが意識する必要があり、設計コストがかかるため、varを利用している。というのは理由として通るのかしら。。

結局、互換性の問題がある限りどれだけ理由をつけてもあんまり意味はない気もします。

Async Await

非同期処理は人を寄せ付ける魅力がありますよね
という事で非同期処理という蜜を味見してみましょう

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => { setTimeout(resolve, ms) })
}

async function main() {
  console.log('start mnain function!')
  await sleep(2000)
  console.log('finish main function!')
}
main()

startログから2秒後にfinishログの単純なロジックですね
そして、Jsは

"use strict";
function sleep(ms) {
    return new Promise(resolve => { setTimeout(resolve, ms); });
}
async function main() {
    console.log('start mnain function!');
    await sleep(2000);
    console.log('finish main function!');
}
main();

皆さんもご唱和ください。
全然変わらない!!!
sleep関数の引数の型注釈がなくなって、無駄な空白行が消えているぐらいですね

考えてみたら当たり前ですよね。。
Jsに型のエッセンスを入れてるのがTsなのだから、型の存在が消えてそれ以外はそのまま踏襲されますよそりゃ。
この企画の場合、Jsでほぼ同じ処理出てきちゃうと面白味がないのでやっぱりJsにはなくてTsにはあるやつをトランスパイルしないとですね

まぁ、空白行の削除というコンパイラさんの重要な仕事が見れただけで良しとしますか。

Decorator

おそらくTsに新しく追加された機能であればあるほどJsのトランスパイス結果は面白くなりそうなので、
普段はあまり使っていませんが、Decoratorを見てみましょう!

function Logger(constructor: Function) {
  console.log(`Class created: ${constructor.name}`);
}

@Logger
class Person {}

PersonクラスをLogger関数でラップしてconsole.logする処理です
Decorator使う意味が皆無ですが、お許しください

そしてJs

"use strict";
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
function Logger(constructor) {
    console.log(`Class created: ${constructor.name}`);
}
let Person = (() => {
    let _classDecorators = [Logger];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    var Person = class {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            Person = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
    };
    return Person = _classThis;
})();

....全然変わらないなんて言わなきゃよかった。。。
まずは落ち着いてコードの実行結果を押さえときましょう
とは言っても、RunするとログにClass created: Personと表示されるだけの処理です。

次は処理の流れを整理しましょう

  1. クラスの初期定義
  2. デコレーター配列の定義
  3. デコレーター処理
  4. メタデータ登録
  5. 初期化関数の実行
    まだイメージがつかめない方も問題はありません
    ゆっくり一緒に見ていきましょう!

クラスの初期定義

let Person = (() => {
    let _classDecorators = [Logger];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    var Person = class {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            Person = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
    };
    return Person = _classThis;
})();

始まりであり終わりはこのコードですね
ひとまず後半のコードは後々出てくるので無視して前半のコードを見ていきましょう

let Person = (() => {
  // 中身省略
})();

Person変数に即時実行関数の戻り値を格納する処理ですね
そのため、今回は私が書いたクラスデコレーターはJsでいろいろ処理が書かれていますが、最終的には「Personを初期化する処理」と言えますね
まだ、Personに何が格納されるのかはわからないので中身を見ていきましょう

デコレーター配列の定義

function Logger(constructor) {
    console.log(`Class created: ${constructor.name}`);
}
let Person = (() => {
    let _classDecorators = [Logger];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    // 以下省略
})();

いくつか変数が宣言 or 初期化されていますね
_classDecoratorsは関数配列が格納されており、今回はLogger関数が要素として格納されています。
ここのLogger関数は私が実装したLogger関数と同一のものです

そのほかの変数はまだ使い道がわからないため、いったん無視して先に進みましょう

デコレーター処理実行準備

    var Person = class {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            Person = _classThis = _classDescriptor.value;
            // 以下省略
        }
    };

変数の宣言が終わると上記の処理が実行されます
Person変数に無名クラスを代入しているようですが、クラス内部の実装はどうなっているのでしょうか?
まず目につくのがstatic {...}という記法ですね

static { _classThis = this; }

これは静的初期化ブロックと呼ばれ、
classの初期化の際に任意の処理を実施しながらclassの初期値を設定したりすることができます
上記の処理は事前に宣言された_classThis変数にthis(ここではPersonクラス)を代入しています。
先の処理を見るとわかりますが、この後、デコレーター関数に自分自身を引数として渡す必要があります。
そのため、一時的にクラスの分身を変数に退避させ、その退避先の変数を引数として渡すことで自分自身を渡せるようにしています

        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            // 以下省略
        }

metadataやSymbolが出てきましたね
処理そのものは_metadata定数に空のオブジェクト or undefined(void演算子)を_metadata定数に入れるものです
気になるのはその前段にある条件ですね

typeof Symbol === "function" && Symbol.metadata

この条件が
Trueなら空のObject
Falseならundefinedが入ります

SymbolといのはJsのES6にて追加された機能で、簡単に言うと「一意な値を作れる関数」です。
Symbolの機能は片手間では語れないそれはそれは興味深い関数のため、詳細が気になる方は公式サイトをご参照いただけると幸いです
また、この記事もわかりやすいのでお勧めです!
https://hacks.mozilla.org/2015/06/es6-in-depth-symbols/

とまぁ、関数であることがわかっていただけたと思いますが、この機能は初期から存在はしません。
つまり、条件の1つ目は実行環境がSymbol関数を使える環境なのかをチェックしていることになります

また、2つ目の条件にSymbol.metadataというのがあります
これもJsに新しく追加された機能で現在は一部環境で実験的に使用することが可能になっています。
metadataは簡単に言うと「処理に影響を与えず関数やクラス、メソッドにメタ情報を提供する機能」です。
ここは一旦ふーんぐらいで、結局2つ目の条件はSymbol.metadataを使うことが可能かどうかをチェックしています。

        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            // 以下省略
        }

_metadataの準備が終わると、いよいよ__esDecorate関数が呼びされます。

デコレーター処理実行

引数

まずは引数から見ていきましょう

            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);

第1引数はnull、こういう時は大体受け入れ側でデフォルトValueとかを使っていますね
第2引数は_classDescriptor変数にオブジェクトリテラルを渡しています。valueプロパティに_classThisをvlaueとして渡しています。
_classThisはPersonクラスの分身でしたね。

第3引数は_classDecorators、これはDecoratorとして渡されている関数の配列でした

第4引数はオブジェクトリテラルを渡し、
第5引数はnull
第6引数は_classExtraInitializers変数を渡しています。今回は空配列ですね

次は、受取側の__esDecorate関数の引数を見てみましょう

var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    以下省略
};

簡単にはいかなかったですね
要素を分解してみていきましょう

A || Bの構文が今回は使われており、Aに値があればAを、Aに値がなくBに値があればBを選択する条件式となっています。

次にAの部分の(this && this.__esDecorate)を見ていきましょう
構造は似ていますが||とは違いA && BはAに値がなければAの値を、Aに値がある場合はBの値を返却する条件式です
実際の中身はthisを使いグローバルスコープにアクセスし、すでに__esDecorateが定義されていないかをチェックしています。
おそらく、もしすでにあるなら再定義せずにその定義を使うことで重複して定義することを避けているものと思われます
この__esDecorate関数はTsでDecorator機能を使う場合その都度呼ばれる関数だと予想されるため、都度定義していてはサイズの無駄になります

最後はBの部分です。

function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    以下省略
};

関数そのものが定義されていますね
引数は6個でこれが受入側の関数定義になります。
グローバルスコープにしろ、直接定義しているfunctionを使うにしろ中身は同じため、コードを読むうえではこの条件分岐は深く考えず__esDecorateは上記の関数が実施される認識でよいと思います

ふぅ...やっと__esDecorate関数の入り口にはたどり着けました。

まだまだ続くのでここまで読んでくださっている方は一度ティータイムなどを取り体を休ませてくださいね
私は仮眠を取ります

処理実態

    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;

ここの処理がわかれば何をしているのかあらかたわかりますね!!
1行目のfcuntionは__deDecorate関数内でのみ使える関数を定義しています(クロージャー)。
あとで使うので今はそっとしておきましょう。触らぬ神に祟りなし。。

    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;

なにやら変数を初期化していますね
kindにはcontextIn.kindを、ここでは固定値のclass文字列が入ります
keyにはkindに応じてプロパティ操作するときに使用するプロパティ記述子を格納しています。
今回はkindがclassのためvalue文字列が格納されます

targetには条件によってctor or ctor.prototype or nullが入ります
今回の場合はctorはnullが入っており、descriptorInはPersonクラスの分身が格納されてるため、nullが格納されます

descriptorにはプロパティ関連を操作するうえで必要な対象オブジェクトのプロパティ情報の格納を行っています。
今回でいうと、すでにdescriptorIn引数にはPersonクラスの分身が入っているためそのままその分身がdescriptor変数に格納されます。

変数の初期化が完了すると、次はデコレーター関数を要素に持つ配列をFor文で順次実行していきます

    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }

for文の中は以下の通りに進んでいきます

  1. contentInからcontentに値を手動でコピー実施(3・4行目)
  2. addInitializer関数を追加して任意の関数を呼び出せるようにする。関数内部ではextraInitializers配列にインスタンス生成時に実行される関数を詰め込んでいます。(5行目)
    ※エラー処理を見るにデコレータの処理が完了したのちに関数を追加できないようにしていますね
  3. デコレーター関数を実際に実行します。引数にはPersonクラスの分身とcontextが渡されますが、私が作成したLoggerクラスは一つしか引数を必要としないので、contextは無視されます。(6行目)
    ※(0,decoretor function)()の記法はthisをundefinedで関数実行するためのカンマ演算子です。これはデコレーター関数がどこから呼ばれても副作用なく動くことを保証するため使用しているものと予想されます
  4. kindがaccessor文字列の場合はgetter/setter用の処理が実施されます。getter/setterの処理は単純にデコレーター関数の結果がオブジェクトであれば、getやset,init関数をその結果で上書きをする処理です。(7~13行目)
  5. デコレーター関数の結果がvoidもしくはfunctionであればフィールド/メソッド用の処理が実施されます。もしフィールドデコレーター関数を定義している場合はこのタイミングでinitializers配列に追加されて初期化時に実施されるようになります。もしデコレーター関数の結果がfunctionの場合はdescriptor[value]に代入することで関数を上書きしています

やっとfor文を抜けましたね。。
上記の処理を登録されたデコレーターの数だけ行われます。今回は1つしか定義していないので1回で抜けますね
ちなみに、私が定義したLoggerは上記の3で実施されます

定義時に実行されるデコレーター関数がすべて終わるとFor文を抜けてdone変数をtrueにしてデコレーター処理が完了します

メタデータ登録

デコレーター処理が完了すると、__esDecorate関数を抜けて以下の処理が実施されます

let Person = (() => {
    let _classDecorators = [Logger];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    var Person = class {
           // 中略
            Person = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            // 中略
        }
    };
    return Person = _classThis;
})();

Person = _classThis = _classDescriptor.value;では、
__esDecorateにて加工したクラスを_classDescriptor.valueから取得し、Personと_classThisに格納しています

if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
_metadataがあれば、_classThisに格納されているクラスにSymbol.metadataプロパティを追加して、その値として、オブジェクトリテラルを定義しています。
metadata仕様が有効な場合はmetadata情報も併せて更新する必要があるため、そのための処理ですね

初期化関数実行

最後は初期化関数の実行です

var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
let Person = (() => {
    let _classDecorators = [Logger];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    var Person = class {
           // 中略
            __runInitializers(_classThis, _classExtraInitializers);
        }
    };
    return Person = _classThis;
})();

__runInitializers関数にはデコレーター処理を施して加工されたクラスとデコレーター関数内で格納しておいた初期化関数を要素に持つ配列を渡しています。
今回は特に初期化時の関数はないので、2つ目の引数のは空の配列が入っています

__runInitializers関数は例のごとくグローバルスコープにすでに__runInitializers関数があるかチェックを行い、あれば使用しなければ直接定義する関数を使用します

__runInitializers関数内の処理はシンプルです。
関数に渡される引数の数に応じてuseValueにtrue/falseを格納
初期化関数配列に基づいてFor文を実行して順次、初期化関数を実行しています。
useValueがtrueであれば引数としてvalueが各関数に渡されます。
今回に関しては初期化関数は定義していないため、__runInitializers関数は空振りして終わります。

Personの返却

let Person = (() => {
    中略
    return Person = _classThis;
})();

最後は加工された_classThisをPersonに代入してリターンしています
そしてリターンした結果がlet Personの変数に代入されて終了です!

お疲れ様でした!

ちょっとした疑問

デコレーターの実装を見てると、いくつか気になる個所がありました

let Person = (() => {
    中略
    var Person = class {
        static { _classThis = this; }
        static {
            中略
            Person = _classThis = _classDescriptor.value; ←なんでPersonに代入してる?
            中略
        }
    };
    return Person = _classThis;
})();

後続でPerson使って処理をしているわけでもないから代入不要では?

いろいろ試してみた!

Enum編

マージ

まずはマージですね!

enum Book {
  nand2Tetoris,
  Hackers,
  Inoveters,
  Monkey,
}

enum Book {
  Hwot = 10
}

そしてJsは?

"use strict";
var book;
(function (book) {
    book[book["nand2tetoris"] = 0] = "nand2tetoris";
    book[book["hackers"] = 1] = "hackers";
    book[book["inoveters"] = 2] = "inoveters";
    book[book["monkey"] = 3] = "monkey";
})(book || (book = {}));
(function (book) {
    book[book["hwot"] = 10] = "hwot";
})(book || (book = {}));

うーん、まぁそりゃそうだよね。という感じですね。
特に大きな驚きもなく
ちょっとまて。マージするときにすでに使われている数値を値に設定したらどうなるんだ?

enum Book {
  nand2Tetoris,
  Hackers,
  Inoveters,
  Monkey,
}

enum Book {
  Hwot = 1
}

どうなる?

"use strict";
var Book;
(function(Book) {
    Book[Book["nand2Tetoris"] = 0] = "nand2Tetoris";
    Book[Book["Hackers"] = 1] = "Hackers";
    Book[Book["Inoveters"] = 2] = "Inoveters";
    Book[Book["Monkey"] = 3] = "Monkey";
})(Book || (Book = {}));
(function(Book) {
    Book[Book["Hwot"] = 1] = "Hwot";
})(Book || (Book = {}));

まぁ、そうなります。
念のため出来上がったオブジェクトを見ると...

{
  '0': 'nand2Tetoris',
  '1': 'Hwot',
  '2': 'Inoveters',
  '3': 'Monkey',
  nand2Tetoris: 0,
  Hackers: 1,
  Inoveters: 2,
  Monkey: 3,
  Hwot: 1
}

しっかり、1プロパティに値が後に記載したプロパティ名と入れ替わっていますね!
同じプロパティ名で値を同じに指定したら、後に定義したほうが勝ち上書きされる。
だだ、すべてが上書きされるわけではなく、プロパティと値のマッピングは残る。

上記の例でいうと、
enumで1を取得しようとすると、HackersではなくHwotが取得されいかにもHackersが消えたように見えるが、enmuでHackersを指定すると依然として1が取得できる。

しっかり、constなどでEnumを守りましょう

文字列Enum

今までは数値を値に取っていましたが、ここでは文字列を使用してます!

enum Book {
  nand2Tetoris = "nand2Tetoris",
  Hackers = "Hackers",
  Inoveters = "Inoverters",
  Monkey = "Monkey"
}

enum Book {
  Hwot = "Hwot"
}

そして

"use strict";
var Book;
(function (Book) {
    Book["nand2Tetoris"] = "nand2Tetoris";
    Book["Hackers"] = "Hackers";
    Book["Inoveters"] = "Inoverters";
    Book["Monkey"] = "Monkey";
})(Book || (Book = {}));
(function (Book) {
    Book["Hwot"] = "Hwot";
})(Book || (Book = {}));

おー!ちょっと変わりましたね!
というかシンプルになりました。
単純に双方向参照がなくなり、一方向参照になっただけですね
つまり、EnumをString型で定義する場合は一方向参照でしかEnumを操作できないという事ですね

終わりに

これはTsの記事なのかJsの記事なのかわからなくなりましたが、
こうやってトランスパイルしてみたり、一つ下のレイヤーをのぞいてみると面白いですよね
Jsコードのvoid 0に歴史を感じました...

プログラミング言語の機能は大体の場合は自然言語で説明されて、あとは使ってみて慣れろ!みたいなことが多いと思いますが、
今回のようにその機能を実現しているコードを読み解くことで自然言語では得られない気づきや理解があるので少し時間はかかってしまいますが、このように取り組むのも面白いですね!

P.S.
ここまで書き終えて改めて読み返していますが、私の文章が荒れ狂ってますね。。
反省します。。。

Discussion