vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

JSConf China 2019 in 上海参加記録( 1 日目)

はじめに

間が空いてしまいましたが、 JSConf China 2019 1日目の内容をメモっておきます。

※ 2019/11/06 会場でメモった内容をほぼそのまま書いています。
意味不明な部分や聞き間違い、解釈違いなど多数あると思います。

もうすぐ資料や動画が配信されるはずなので、その後更新する予定です。

会場

まずは会場の様子から。

f:id:mslGt:20191109063304p:plain

f:id:mslGt:20191109063325p:plain

ステージ

  • 中央にステージと大きなスクリーンがあり、左右に少し小さめのスクリーンがありました。
  • 英語セッションのときは中央のスクリーンの両端に、中国語訳がつけられていました。

椅子など

  • パイプ椅子が並んでおり、机は最前列のみ設置されていました。
    (長時間座っているとお尻が痛い(´・ω・`))
  • 端の方には休憩スペースがあり、休憩時間にはコーヒーを飲んだり転がったり。。
  • 休憩時間中、席に PC を置いて行く方がちらほら。信頼のあらわれ?面倒だっただけ?
  • みんな Mac を使っていたのでちょっと欲しくなるなど。

参加者について

  • 男女比は 9:1 くらい。参加者はざっと 300 人ぐらい。
  • 欧米系の方もちらほら見られた。スタッフをしてくれている方も。

各セッションの言語

  • 8 割くらい英語で行われていた。国際的なカンファレンスだから?海外ゲストにもわかるように?
  • ただし、観客の方は(自分の周囲だけかもだが)最後の方はしんどそうだった。わかる。わかるよ。
  • パネルディスカッションのときは司会の方が通訳したりもしていたが、間に合わず端折るところも。ですよね~。

Opening & Opening Note

  • 今年で 10 年目 & 7 回目(毎年開催しているわけではない)
  • 10年前には React も GraphQL もなかった。
  • 中国内の各地で開催しており、上海の他北京、深センなどでも。
  • カンファレンスの理念は、開発者同士でコミュニケーションを取ること。
    • Web をめぐる開発環境はどんどん進化して便利になっているが、コミュニケーションについては十分とは言えない
  • Opening にて Wechat でグループを作り、皆で参加する。
    • 人数は 120 人くらいに
    • 質問、休憩時間などでの連絡、終了後の連絡などで使われている

Keynote. Continuous learning & leadership evolution

Video

※このカンファレンスのものではありませんが、同じタイトルで別のカンファレンスで話ししている動画があったため載せておきます。

Optimized for cost

  • deliver technology
  • stranger pattern
  • deliver value team
  • move up team
  • 将来的に何を選ぶ
  • 客やこれまでの経緯など、とりまく環境の共有

チームをどうまとめていくか

  • チームごとに担う仕事はバラバラである。特に QA チームは完全に開発チームから離れている
  • これらをどうまとめていくか

  • product model

  • A3 problem solving
  • Speed to value
  • DevOps
  • 計測しないと解決できない
  • focus just speed
  • 新しい環境に移ったら、90 日で慣れる?
  • 実際のグラフ、カンバン?の様子なども見せてくれていた。
  • Roleも大切
  • global technology
  • 他のチームに入っててこの役割
  • data with context drives clear
  • sharing success
  • 以前は失敗とはネガティブなものだった
  • who made change
  • 組織ごとに異なる
    • それに合わせる
  • leadership need evolve

2. The beauty of TypeScript

Slide

最も良い言語は何か?

  • 状況による。例えば 128kb RAM の環境なら C が選ばれるだろう
  • フロントエンドなら JavaScript/TypeScript
  • 高开发なら GolangJava (C#Python の人気は今ひとつのよう)

どういう状況だと TypeScript が一番輝くか?

  • 開発者が多い
  • 開発する項目数が多い
  • TypeScript ならどんな JavaScript も TypeScript として扱うことができる

TypeScript の使用状況

  • 使ったことのある人は八割くらい。
  • 殆どの業務で TypeScript を使う人は四割くらい

JavaScript の辛いところ

JavaScript のプロジェクトを TypeScript に変換するときの便利ツール?

  • コードに下記を追加すると VS Code で処理を実行してくれるらしい

@ts-ignore

  • TypeScript に変換したときのコンパイルエラーを消してくれる

@ts-nocheck

  • @ts-ignore の処理をまとめて実行してくれる

@ts-check

  • JavaScript のコードに対して実行し、 TypeScript に変換したときにコンパイルエラーになるところを検出してくれる。

3. ClojureScript - Winning through simplicity

Problem of JavaScript

  • increase complexy
  • デバッグしにくい
  • window Scope -> TypeScriptと同じような問題

Choose simplicity over complexity

Compile はBavel で慣れてるでしょ?

Simplest language?

  • Lisp!: (function argument)
  • プログラムしないのが最強
  • s-Expression
  • tree/graph

Lisp

  • Lisp はシンプルだけでなく古い(歴史がある)
  • John McCarthy

Paul Graham

Brendan Eich

Clojure

ClojureScript

  • Clojure compiled to JS
  • リリースはRichHickeyによりOctober 2007に
  • Functional
  • Recursion
  • メジャーなエディターなら対応してる
  • インデントが意味を持つ(Python的な)
  • REPL: Command line
  • EDN

    Database

  • Datomic
  • DataScript
  • Datalog

  • bnomis (Simon Blanchard) - GitHub

summary

4. Modern GraphQL workflow

Slide

これまで

  • サーバーはリクエストに対して全データを返す
  • 必要なのはそのうちの一部(ほかは無駄になってしまう)

GraphQL

  • 必要な分だけ返す
  • リクエスト側にも見分けるための仕組みがある?

  • graphql

  • graphql-compose
  • merge-graphql-schemas
  • graphql-middleware
  • apollo-server

GraphQL Joker

summary

  • GraphQL の紹介(REST -> GraphQL への流れから)
  • GraphQL Joker の紹介

5. Building (Progressive) Web Apps

  • MaaS

What the hack PWA

  • Fast
  • Integrated
    • touch, GPS, etc.
  • Reliable
  • Engaging

Why PWAs?

  • 使用時間のうち87%がNative Appsを使っている

Shun the non PWA

  • ネイティブ的にテーマカラーに合わせるとか、そもそもJavaScriptがオフにされていたら無理
  • Offline時にレスポンスが遅い

Dev tool

Being Reliable

Angularの場合

  • ng add @angular/pwa
  • React, Vueもかんたんに入れられる

Workbox

Being Faster

Being Engaging & Interactive

6. 面向传统, Serverless 进化之路

Slide

memo

  • 阿里巴巴のサービスをServerlessにしたときの効果、苦労や手順など。
  • BFF 70%
  • Node.js
  • マシンや人の負荷を下げたい
  • すでに過去の遺産がたくさんある
  • Serverless への移行は 2018/10 に準備を開始した
    • 淘宝网,飞猪大BU に反映
  • コストは 100% -> 70% -> 40%
  • Ops のリソース
  • Dev 側は開発完了後はリソース不要(本当に減らすとリスクが高いため減らすことはできない)
  • Note.js のバージョン固定
  • 研究開発のリソース
  • 多能工
  • 開発者、部署が少なくてもできる
  • サーバーへの負荷軽減
    • 今までは Controller がひとまとめに受けてきたが、 Edge -> 業務ロジック -> データ と個別にアクセスできる
      • 負担軽減へ
  • クラウド環境での統合テスト
  • アップグレードモデルの研究開発
  • これまでの期間は一年、まだ完了はしていない
  • FaaS + BaaS
  • Egg / Mildloway Server
  • Http Server

7. Evolution of Quality, Support, Releases at NIKI

How we've transformed in 5 years

  • modernization - microservices / cloud
  • continous delivery
  • follow the sun / GEO and global
  • cultural shift

Memo

  • 綿密なテストを重ねて失敗を責める文化 -> 早く小さく失敗する文化へ
  • DevOps
  • Engineering Mindset
  • SRE
  • Fire Fighting
  • Share Ownership

From -> To

  • 長くテストをして長い時間をかけてリリースしてた -> CI/CD による早いリリース
  • 手動でテスト -> 自動化、Global Execution?、On demand test environments, sv

8. 响应式编程使你看得更远

Slide

過去話

  • RxJsは今までも毎回テーマに上がっていたらしい。どんな新しい知識が得られるか?といった紹介からスタート

Rxについて

Memo

  • callback -> promise
  • async/await
  • Message Driven / Responsive / Resilient / Elastic
  • Observables
  • Observer
  • MergeMap -> Stream に流れる○をマージする?
  • throttleTime(25)
  • debounce & throttle
  • switchmap - 競合問題の解決?
  • CombineLatest
    • withLatestFrom
  • TakeUntile
    • Cold Observable
    • Hot Observable
  • Share with "Subject"
  • rxjs-hooks
  • redux-observable
  • Cycle.js
  • 人と境界

Panel Discussion

概要

  • Wechat で参加者から集めた質問を、登壇者に投げかける
  • 質問は英語だったり中国語だったり
  • 司会の方が通訳をしていたが、端折ったり翻訳しないことも(まぁ大変すぎですよね)

TypeScript をどのように導入するか

  • 一度に全部切り替えるのではなく、一部から期間を決めて導入する
  • 阿里巴巴では大部分で TypeScript を使っている(ここ三年くらい)
  • 導入のメリットを鑑みる
    • APISDK で何をしたいのかが伝わりやすい
    • ただしまだハードルが高い場合がある
  • 新人も助ける

ClojureScript が強いところ

  • 特定の場面で、というわけではない
  • 複雑なデータや仕組みをもつものに使うのが良い

GraphQL

  • fluent
  • filtering
  • reasonable request
  • DB にとっても良いところ?

How the computability of PWAs

GraphQL には swagger や OpenAPI のような標準がある?

  • Browse Schema であり、 API ではないのでそのようなものはないのでは?

ThrottleTime と DebounceTime との違い?

JSConf China 2019 in 上海参加記録( 0 日目:移動日・上海旅行編)

はじめに

10/19~10/20 に上海で行われた、 JSConf China 2019 に参加してきましたので、メモっておくことにします。

といっても今回は 10/18 の移動 + ひさびさに上海(南京東路 - 人民広場あたり)を旅行した話です。

グレートファイアウォールを乗り越える

中国に来るときに面倒なのがグレートファイアウォールの存在。

TwitterGoogle などのサービスが使えないと、それに依存している私のような人間は困ってしまう、と。

一般的に使用されるのは VPN で、友人におすすめされた 12VPN というのを一ヶ月使えるようにしてみました。

また、モバイル Wi-Fi をイモトで借りることにしたのですが、中国プレミアム回線という TwitterYoutube が使えるサービスがある、ということで、そちらも試してみることにしました。

結果ですが、 WiFi の方だけで問題なく接続できていました。

ただ、専用回線?に接続詞にいっているためなのか、つながるまでに一分程度待つ必要がありました。

まぁ無理やり接続しているものには変わりないと思うので、その辺は仕方がないですね。

というわけで VPN はあまり使いませんでしたが、 Google Hangout などがつながらない場合に VPN を使うといける、という場合もあったため、まぁ両方ある方が安心かな~といったところ。

なお、500MB/日 という容量は、画像などのやりとりをしていると結構すぐ超えてしまうため、心配な場合はもう少し多いプランにしておくのが良さそうです。

キャッシュレス決済

中国ネタでよくあがる(特に中国スゴイ的な内容の場合)キャッシュレスの話。

せっかくだし使ってみたくはあったのですが、中国の銀行アカウントや携帯番号がないと利用できないという問題が( JSConf のチケット購入もこれで困りました)。

ということで、現金のみで頑張ることにしました。

結果としては、まぁなんとかなる、という感じです。

地下鉄の切符購入や食事など、問題なくできました。

ただ、キャッシュレス決済の方が明らかに便利そうな様子を見ながら現金払い(日本のICOCA 的な交通カードも現金だと購入できず)、という若干の辛さとか、お、お前キャッシュレス決済じゃないのか(まぁ中国に住んでる同年齢の人ならほとんど使っているでしょうから)的に言われる煩わしさはありました。

単に気にするかどうかぐらいのところではありますが。

本屋(上海書城)

初日は若干時間があった、ということで、本屋にも行ってみることにしました。

プログラミング関連の本の傾向は日本と違うのでしょうか。

Golang は数は少ないものの、 JSConf のセッション中の話によると、結構使われているようです。

ということで、状況は日本とあまり変わらないようです。

写真

あとは旅行っぽく写真でも。

f:id:mslGt:20191018144553j:plain

f:id:mslGt:20191024074313j:plain

f:id:mslGt:20191024074551j:plain

雑に Angular 再入門(Tour of Heroes App) 1

はじめに

色々あったりなかったりして、 Angular をもう一度触ってみることにしました。

Angular 2 の時代にハンズオンに参加したとはいえ、あれからずいぶん経っているわけなので、もう一度一からやってみるぞい!と思ってサイトを確認したところ、 Angular 8 の今も Tour of Heroes App は現役のようです。

(GETTING START が別にあるのですが、こちらはあらかじめ用意された環境を触ってみる、という感じだったので後にまわすことにしました)

ということで、気になるところは寄り道しつつツアーにお出かけしてみることにします。

準備

まずは準備から。

といっても TypeScript 触っていた影響から Node.js の環境はそろっているため、必要なものは Angular CLI だけです。

npm install -g @angular/cli

VS Code拡張機能としては Angular Language Service だけ入れてみました。

で、プロジェクトを作成します。

ng new angular-tour-of-heroes
cd angular-tour-of-heroes

※本当は ng-sample のような雑な名前にしていましたが、とりあえず合わせてみました。

Error

複数台で試したのですが、以前入れていたものが悪かったのか、 ng new の途中でパーミッションがないとエラーになりました。

が、 npm -g update のあと Angular CLI をインストールしなおしたら直りました。

Angular routing、CSS Framework

ng new を実行すると、 Angular routing を追加するかと CSS Framework を使うかを聞かれます。

  • CSS
  • SCSS
  • Sass
  • Less
  • Stylus

今回はルーティングはツアー中に追加するため追加せず、 CSS もサンプルコードに合わせて素の CSS を選択しました。

以前 PostCSS を使いましたが、 CSS Framework も近いうちに試したいところ。

実行してみる

とりあえず実行してみます。

ng serve --open

--open をつけることで、起動後に Web ブラウザでページを開いてくれます。便利!

...は良いのですが、

f:id:mslGt:20191009190554j:plain

Oh... ナンデコンナニゴウカナノ?

レスポンシブデザインにもなっているという気の利きよう。

どこかの Web Framework を思い出しました。

Empty テンプレートのようなものはあるのでしょうか。

空のページを作る

とりあえず今回は手動で空のページを作ってみます。

とはいえ、実は変更が必要なファイルは一つだけです。

  • src > app > app.component.html

CSS も含め先ほど表示された内容はすべて HTML ファイルに書かれているため、(若干の罪悪感はありつつも)丸ごと削除してやれば空のページが出来上がります。

まぁテスト( app.component.spec.ts )は失敗するようになるわけですが、一旦放置の方向で。

ともあれ、さっそく順番に進めていきますよ。

Directive って何

Introduction の、このチュートリアルを通してできるようになることの中に下記があります。

Angular のビルトインの Directive を使ってヒーローリストの要素を表示・非表示する

...Directive ってナンデスカ?

Angular.js のドキュメントでは

Angular のドキュメントでは Directive 自体についての説明が見つからず、 Angular.js 時代のドキュメントでは、DOM 要素に Angular 特有のふるまいを持たせるためのマーカー、となっていました(と認識)。

@Directive の説明に DOM 要素にカスタムのふるまいを持たせるのに @Directive が使える、というような説明があるため、 Directive の役割自体はあまり変わっていないのだと思われます。

あまり深入りしてもアレなので話を次に進めますが、 Directive には大きく3種類あります。

なぜ Component だけ Component "directive" でないのか、という疑問もありますが、それは置いといて、 Component から順に調べてみますよ。

Component

この 3 種類の中で最も頻繁に使用される Component 。

上記 Getting Started に出てくるもののことを指しているのだとすると、 ng generate component {Component 名} で生成される、下記 3 つのファイルからなる Component となります。
(テストファイルである heroes.component.spec.ts も生成されますが、 Component としては含まれないようです)

ng generate component heroes の場合

  • Component class (heroes.component.ts)
  • HTML template (heroes.component.html)
  • Component-specific styles (heroes.component.css)

まどろっこしい言い方をしているのは、 Component class の中に下記があるためです。

heroes.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
    ~省略~
}

どっちを指しているのか迷うところですが、前者は app.module.ts の @NgModule > declarations に Component class が定義され、ベースとなる HTML (app.component.html) に < app-heroes>< /app-heroes> が追加されることで動作します。

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';

@NgModule({
  declarations: [
    AppComponent,
    HeroesComponent,
  ],
~省略~

app.component.html

< h1>{{title}}< /h1>
< app-heroes>

強引に .NET に例えるならば下記のようになるでしょうか。

  • Component class -> コードビハインド
  • HTML template -> XAML
  • Component-specific styles -> デザインファイル

先ほどの Directive の役割が DOM 要素に Angular 特有のふるまいを持たせる、というところから見れば、やはり前者を指していると考えてよさそうです。
(間違ってたら訂正します)

Component-specific styles

とりあえず知りたいことは山ほどあるわけですが、比較的簡単に確認できそうなところから。

ページ全体に影響するようなスタイルは styles.css に書きますが、特定の Component のみに影響するようなものは Component-specific styles に書くと。

最終的には HTML として出力するわけですが、その上で他の Component に影響しないようにする方法とは? と思ったので、とりあえず試してみました。

heroes.component.css

.heroes {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
}

app.component.html

< app-heroes>
< div class="heroes">hello< /div>

こうするとどうなるか。

heroes.component.cssheroes クラスは、下記のように変換されます。

.heroes[ _ngcontent-vyr-c1] {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
}

そして app-heroes 以下の要素は(例えば div があったとすると)下記のようになります。

< app-heroes>
    < div _ngcontent-vyr-c1>hello< /div>

で、 app.component.html に書いた div はこうなると。

< div _ngcontent-vyr-c0>hello< /div>

ということで CSS のクラス名が異なるため適用されない、と。

なるほどねぇ~。

一旦切ります。

(まぁこういうことしているから終わらないんだよねぇ~。楽しいから良いのだけれども)

TypeScript で関数オーバーロード

はじめに

C# だと時々使う関数のオーバーロード

TypeScript で同じように書くとエラーになるため、使えないのかな~と思っていたのですが、よくよく調べてみると使えるらしい。しかしながらちょっと様子が違う?となったので、あれこれ試してみることにしました。

オーバーロード

まず C#オーバーロードから。

Sample.cs

public class Sample
{
    public string GetMessage(string message)
    {
        return "hello";
    }
    public int GetMessage(int message)
    {
        return 200;
    }
}

各メソッドはメソッド名と引数の型、数で区別され、別物として扱われます。

戻り値のみ異なる場合はエラーになります。

このコードをそのまま TypeScript で書いてみると。。。

Sample.ts

class Sample{
    public getMessage(message: string): string{
        return message;
    }
    
    // エラーとなる.
    public getMessage(message: number): number{
        return message;
    }
    
}

Duplicate function implementation と怒られてしまいます。

C# に合わせてクラスのメソッドを見ていますが、関数の場合も同じです。

理由は返還後の JavaScript を見ればすぐにわかります。

Sample.js

class Sample {
    getMessage(message) {
        return message;
    }
    getMessage(message) {
        return message;
    }
}

型の情報が消えてしまうため、まったく同じメソッドになってしまう、というわけですね。

ただ、引数の数を変更しても同じようにエラーとなるのは気になるところですが。。。

プロパティなどと同様、動的に変わるものということで、メソッドを判別する要素として使用していない、ということなのでしょう。

とはいえ TypeScript にもオーバーロードはあります

では TypeScript にはオーバーロードはないのか?というと、そうではありません。

Sample.ts

class Sample{
    public getMessage(message: string): string;
    public getMessage(message: number): number;
    public getMessage(message: any): any{
        return message;
    }
}

※ ググった内容から、昔は戻り値が同じ型である必要があったようですが、少なくとも ver.3.6 では異なっていても問題ありません。

最初見たときちょっと驚きましたが、重要なのは実際の処理を行う引数・戻り値が any のメソッドです。

今回はオーバーロードしているメソッドの戻り値が string と number であるため any としていますが、下記のように、オーバーロードしている全メソッドの戻り値の型の親となる型であれば OK です。

Sample.ts

class Sample{
    public getMessage(message: string): "hello";
    public getMessage(message: number): "world";
    // 戻り値は string で OK
    public getMessage(message: any): string{
        return message;
    }
}

なお、 JavaScript に変換した結果は下記の通りです。

Sample.js

class Sample {
    getMessage(message) {
        return message;
    }
}

実際の処理を書いているメソッド以外のオーバーロードの内容はすべて消されてしまうわけですね。

引数の型によって処理を変えたいときもあるよね

やったね TypeScript でもオーバーロードができた!

。。。と言いたいところですが、これだと型が違っても全く同じ処理をすることしかできません。 (全く違うことをするのであれば別のメソッド・関数にすべきだとは思いますが)

となると、いったん引数を any で受け取って、その型を調べて処理を分ける、という方法をとる必要がありそうです。

型を調べる

引数の型に合わせて処理を分けるには、当然ながらその引数の値の型を調べる必要があります。

typeof

string や oject などの基本型は、 typeof(引数) で調べることができます。

  • string
  • number
  • bigint
  • boolean
  • symbol
  • undefined
  • object
  • function

Sample.ts

class Sample{
    public getMessage(message: string): string;
    public getMessage(message: number): number;
    public getMessage(message: any): any{
        if(typeof(message) === "string"){
            console.log("message type is string: " + message);
        }
        if(typeof(message) === "number"){
            console.log("message type is number: " + message);
        }
        return message;
    }
}
let s = new Sample();
s.getMessage("hello");
s.getMessage(30);

出力結果は下記の通りです。

message type is string: hello
message type is number: 30

自作の型・interface・クラス・関数

自作の型・interface・クラス・関数を調べたい場合はどうすればよいでしょうか。

typeof の結果は object となります。

Sample.ts

let s = new Sample();
// object と出力される
console.log(typeof(s));
// コンパイルエラー
console.log(typeof(s) === "Sample");

というわけで、 typeof を使用することはできません。

ではどうするか。

上記ドキュメントによると、このようにして比較するようです。

Sample.ts

type TypeSample = {
    id: number,
    name: string,
};
class Sample{
    private isType(message: any): message is TypeSample{
        return (message as TypeSample).id !== undefined;
    }
}

えぇ。。。

as

ちなみに message as TypeSample だけじゃダメなの?というと。。。

let id = 30;
let t = (id as any as TypeSample);
console.log(t);

一旦 any にキャストしているのは number のままだとコンパイルエラーになるためです。

この出力結果は下記の通り。

30

えぇ。。。 undefined とかじゃないの。。。

実は JavaScript に変換すると as ~ が削除されるため、下記のようになります。

let id = 30;
let t = id;
console.log(t);

そりゃ undefined にはならないですよね~/(^o^)\

あと、引数が指定の型( TypeSample )である、という表現が、 message is TypeSample となるのは面白いですね :)

プロパティ選びは慎重に

この判別方法の課題は、同じプロパティを持っていると区別できない、という点です。

Sample.ts

type TypeSample = {
    id: number,
    name: string,
};
class Sample{
    public getMessage(message: TypeSample): TypeSample;
    public getMessage(message: Function): Function;
    public getMessage(message: any): any{
        console.log(this.isType(message));
    }
    private isType(message: any): message is TypeSample{
        return (message as TypeSample).name != null;
    }
}
let s = new Sample();

// 1. TypeSample を渡した場合.
s.getMessage({
    id: 20,
    name: "たこ焼き"
});

// 2. Function を渡した場合.
s.getMessage(() => console.log("チーズ"));

この実行結果は両方とも true になります。

これは、関数がデフォルトで name というプロパティを持っているためです。

とすると、

Sample.ts

type TypeSample = {
    id: number,
    name: string,
    type: "TypeSample"
};
const TypeNameTypeSample: "TypeSample" = "TypeSample";
class Sample{
    ~省略~
    public getMessage(message: any): any{
        console.log(this.isType(message));
    }
    private isType(message: any): message is TypeSample{
        return (message as TypeSample).type === TypeNameTypeSample;
    }
}
let s = new Sample();

s.getMessage({
    id: 20,
    name: "たこ焼き",
    type: "TypeSample"
});
s.getMessage(() => console.log("チーズ"));

。。。なんだろうこの敗北感。

まぁ string 型にするよりはマシとはいえ、面倒くさいし間違いやすいし。

そもそも判別しうるのか

たとえば type のインスタンスを作って中身を覗いてみると。。。

let t: TypeSample = {
    id: 20,
    name: "たこ焼き"
};
console.log(t);

中身はこのようなものになります。

{…}
    id: 20
    name: "たこ焼き"
    < prototype>: {…}
        __defineGetter__: function __defineGetter__()
        __defineSetter__: function __defineSetter__()
        __lookupGetter__: function __lookupGetter__()
        __lookupSetter__: function __lookupSetter__()
        __proto__: 
        constructor: function Object()
        hasOwnProperty: function hasOwnProperty()
        isPrototypeOf: function isPrototypeOf()
        propertyIsEnumerable: function propertyIsEnumerable()
        toLocaleString: function toLocaleString()
        toSource: function toSource()
        toString: function toString()
        valueOf: function valueOf()
        < get __proto__()>: function __proto__()
        < set __proto__()>: function __proto__()

はい、「 TypeSample 」という型情報がありませんね。

type 、 interface は、例えばあるクラスインスタンスが同じプロパティを持っていれば、その type 、 interface を実装していると見なされます。

そのため、 TypeSample 型であるか、ということは、プロパティからしか判別できないと。

もしプロパティ一つだと判別できない、という話であれば、全プロパティを確認しても良いかもしれません(あまりそのシチュエーションはなさそうですが)。

Sample.ts

~省略~
const TypeSampleChecker: TypeSample = {
    id: -1,
    name: ""
};
class Sample{
    ~省略~
    public getMessage(message: any): any{
        console.log(this.isType(message));
    }
    private isType(message: any): message is TypeSample{
        // TypeSample の全プロパティをチェック.
        for(let p in TypeSampleChecker){
            if(message[p] == null){
                return false;
            }
        }
        return true;
    }
}
let s = new Sample();
s.getMessage({
    id: 20,
    name: "たこ焼き"
});
s.getMessage(() => console.log("チーズ"));

ではクラスインスタンスはどうか

ではクラスインスタンスの場合はどうかというと、 instanceof で判定できそうです。

Sample.ts

~省略~
class ClassSample{
    public id: number = 99;
    public name: string = "めんたい";
}
class Sample{
    public getMessage(message: ClassSample): ClassSample;
    ~省略~
    public getMessage(message: any): any{
        console.log(this.isClass(message));
    }
    ~省略~
    private isClass(message: any): message is ClassSample{
        return message instanceof ClassSample;
    }
}
let s = new Sample();
s.getMessage({
    id: 20,
    name: "たこ焼き"
});
s.getMessage(() => console.log("チーズ"));
let f = () => console.log("コーンポタージュ");

s.getMessage(new ClassSample());

結果は下記の通り。

false
false
true

良いですね :)

ClassSample を継承した場合はどうでしょうか。

Sample.ts

~省略~
class ExtendClass extends ClassSample{
}
s.getMessage(new ExtendClass());

これも true になりました。

もし継承したクラスを除外したい場合、 message.constructor.name でクラス名を取ると良いかもしれません。

念のためプロパティが同じ別のクラスも見ておきます。

Sample.ts

~省略~
class OtherClass{
    public id: number = 99;
    public name: string = "めんたい";
}
s.getMessage(new OtherClass());

結果は false になります。

結果自体は良いのですが、メソッド(関数)の引数としては、クラスが異なっても同じプロパティを持っていれば渡すことができてしまうのですね。

地味に驚きました。

まとめ

これらを組み合わせると、オーバーロードしたメソッドの処理を引数の型に合わせて切り替えられそうです。

Sample.ts

type TypeSample = {
    id: number,
    name: string,
};
class ClassSample{
    public id: number = 99;
    public name: string = "めんたい";
}
const TypeSampleChecker: TypeSample = {
    id: -1,
    name: ""
};
const ClassSampleChecker = new ClassSample();

class Sample{
    public getMessage(message: string): string;
    public getMessage(message: number): number;
    public getMessage(message: ClassSample): ClassSample;
    public getMessage(message: TypeSample): TypeSample;
    public getMessage(message: Function): Function;
    public getMessage(message: any): any{
        switch(typeof(message)){
            case "string":
                console.log("[type:string] " + message);
                break;
            case "number":
                console.log("[type:number] " + message);
                break;
            case "function":
                console.log("[type:function] " + message);
                break;
            case "object":
                // type、classの判別.
                if(this.isClass(message)){
                    console.log("[type:ClassSample] " + message);
                }
                else if(this.isType(message)){
                    console.log("[type:TypeSample] " + message);
                }
                else{
                    console.error("type not found");
                }
                break;
            default:
                console.error("type not found");
                break;
        }
        return message;
    }
    private isType(message: any): message is TypeSample{
        // TypeSample の全プロパティをチェック.
        for(let p in TypeSampleChecker){
            if(message[p] == null){
                return false;
            }
        }
        return true;
    }
    private isClass(message: any): message is ClassSample{
        return message instanceof ClassSample;
    }
}
let s = new Sample();
// string
s.getMessage("サラミ");
// number
s.getMessage(97);
// ClassSample
s.getMessage(new ClassSample());
// TypeSample
s.getMessage({
    id: 20,
    name: "たこ焼き"
});
// Function
s.getMessage(() => console.log("チーズ"));

結果は下記の通り。

[type:string] サラミ
[type:number] 97
[type:ClassSample] [object Object]
[type:TypeSample] [object Object]
[type:function] () => console.log("チーズ")

。。。動作には問題がないのですが、どうでしょうこの溢れ出る別メソッドに分けろよ感は。

ご利用は計画的に( ˘ω˘ )

WSL の Ubuntu 18.04 で SpiderMonkey をビルドした話

はじめに

ここまで TypeScript(JavaScript) の prototype などを追ってきて、ふと思ったのが、「これって内部的にはどう扱われているのだろう」ということでした。

そこで、 JavaScript を動かすのに使われる JavaScript Engine を触ってみることにしました。

選択の理由

最初 Windows 上で V8 を使ってみようと思ったのですが( Node.js も V8 を使っているということで)、ビルド自体はできたものの、そこからどうしたら・・・?ということで、いったん中断しました。

で、大好きな FireFox でも使われているという SpiderMonkey を試してみることにしました。

これも最初は Windows 上でビルドしようとしたのですが、途中で登場する autoconf2.13 が、どれを使えば良いのか?となったので、 WSL 上の Ubuntu 18.04 を使うことにしました。

WSL のインストール

あえて書くほどのこともないですが。

コントロール パネル > プログラム > プログラムと機能 > Windows の機能の有効化または無効化

から、 Windows Subsystem for Linux にチェックを入れて再起動します。

せっかくなので WSL 2 もインストールします。 (ビルドバージョン 18917 以上でないと、コマンド実行時にエラーになるので注意が必要です)

あとはディストリビューションMicrosoft Store でインストールするだけです。

今回は Ubuntu 18.04 LTS にしました。

雑にユーザー名とパスワードを登録し、 sudo apt update -> sudo apt upgrade をキメておきます。

Windows <--> WSL(Ubuntu) のファイルのやり取り

Windows と WSL でファイルをやり取りするのは、特別なことをしなくても可能です。

この辺り VM にはない便利さがありますね。

Windows -> WSL(Ubuntu)

Windows 上では下記の場所にデフォルトのディレクトリがあります。

  • C:/Users/{Windows のユーザー名}/AppData/Local/Packages/CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc/LocalState/rootfs/home/{Ubuntu のユーザー名}

Ubuntu18.04onWindows_ の後ろの部分はインストール時に決まるのでしょうか。

または、アプリのバージョンによっている?

WSL(Ubuntu) -> Windows

WSL 上からは、 /mnt/c が C ドライブになる、と。

ただ、最初 Windows 上に SpiderMonkey のソースを git clone していたため、それを使ってビルドしようとしたら途中で失敗したため、アクセス権的なところで問題があったかと思います。

なので、インストールやビルドするファイルは素直に WSL 上に置くのが良いかと思います。

必要なソフトウェアのインストール

次は SpiderMonkey のビルドに必要なソフトウェアをインストールしていきます。

インストールするものは下記に書かれているわけですが、デフォルトでインストール済みのものと区別がつかない、というところもあって、トライ&エラーで怒られたら足りないものをインストール、という方法で行ったため、不要なもの、重複するものが混じっていたりすると思います。

とにかくインストールしたものは下記の通りです( Rust, Cargo は後述)。

  • autoconf2.13 (普通に autoconf でインストールすると 2.6X とかが入るので注意)
  • python (Python3 系だとちゃんと動かないらしいです)
  • clang
  • gcc
  • g++
  • yasm
  • llvm
  • make
  • clang-8 (多分不要)
  • gcc-6 (多分不要)

Rust, Cargo のインストール

SpiderMonkey のビルドには Rust, Cargo が必要です。

が、 apt get install でそれらを入れてしまうと、バージョンが低いとエラーになります。

ver.1.37.0 が必要なわけですが、これをインストールするのは、またリポジトリー追加するのかな~と思っていたら、少し違っていました。

上記を参考に、コマンドを実行していくだけです。

curl https://sh.rustup.rs -sSf | sh
ls $HOME/.cargo/bin
cat $HOME/.cargo/env
source $HOME/.cargo/env

せっかく環境揃ったのだし、 Rust も触ってみても面白そうですね(収拾付かないので今はできませんが)。

SpiderMonkey のビルド

いよいよここからが本番ですよ。

基本的には MDN のドキュメントを参考に進めていくだけです。

ソースコードは git からも入手可能ということで、下記を参考に git clone しました。

ディレクトリをホームに追加して、その下で git clone 。

cd gecko-dev したところからスタートです。

  1. cd js/src
  2. autoconf2.13
  3. mkdir build_OPT.OBJ
  4. cd build_OPT.OBJ
  5. ../configure
  6. make

3.、4.は不要かもしれません(エラーで苦しんでいるときにあれこれ参考にしたサイトの真似をしています)。

ともかく、エラーが発生することなく make が完了すれば OK です。

それにしても、 make の際、ログが固まりでドン!と出てくるのが心臓に悪い(´・ω・`)

実行する

実行ファイルは gecko-dev/js/src/build_OPT.OBJ/js/src にある js です。

ということで、 (build_OPT.OBJ にいる場合) ./js/src/js と実行してやれば >js と表示され、 console.log("hello world!"); と入力してやれば hello world! と表示されます。

f:id:mslGt:20190915101204j:plain

さて、動くようになったのは良いとして、コンパイルした結果を見る、 JavaScript のコードを直接書くのではなく、ファイルを読み込む方法は? など試していきたいと思います。

【TypeScript】prototype を追いかける

はじめに

続きです。

が、 Decorator から離れていく気しかしないのでタイトルは変えておきます。

prototype メソッド

mainPage.ts

export class Sample{
    private message = "たこやき";
    public callMethod(){
        console.log("I love " + this.message);
        this.message = "チーズ";
    }
    public static callStaticMethod(){
        console.log("I love " + Sample.prototype.message);
        Sample.prototype.message = "コーンポタージュ";
    }
}
~省略~

前回のコードですが、 prototype メソッド( callMethod() )が持つ Object(prototype) をたどってみるため、下記のようにしてみます。

class Sample{
    public callMethod(){
        ~省略~
    }
    
    public getThis(){
        return this;
    }
    
}

let sample = new Sample();

console.log(sample == sample.getThis());

console.log(sample.callMethod);
console.log(sample.callMethod.prototype);
console.log(Object.getPrototypeOf(sample.callMethod));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(sample.callMethod)));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(sample.callMethod))));

まず上記のコードにおいて、 prototype method である callMethod() や getThis() の中で this と書いた場合、その中身は Sample のインスタンスです( sample )。

そのため、sample.callMethod と callMethod() 内での this.callMethod も同じです。

ということで上記は、sample.callMethod のみで確認しています。

実行結果は以下の通り。

true
function callMethod()
undefined
function ()
Object { … }
null

console.log(sample.callMethod) の中身は function callMethod() となっています。

また、その下の Object.getPrototypeOf(sample.callMethod) を繰り返すところを見てみると、下記のようになっています。

function callMethod() > Function.prototype > Object > null

流れとしては関数(メソッド)のプロトタイプチェーンで説明されている通りかと思います。

{インスタンス}.{prototype メソッド}.prototype = undefined?

一つ気になるのが sample.callMethod.prototype 。

なんで undefined になるの?というか、こないだ実行したときそうなってなかったような。。。?と思っていたら、ターゲットを ES2015 以上に設定している場合、返還後の JavaScript でもクラスが使用されるのですが、その場合 undefined になるようです。

ES2015 未満

"use strict";
var Sample = /** @class */ (function () {
    function Sample() {
    }
    Sample.prototype.callMethod = function () {
    };
    Sample.prototype.getThis = function () {
        return this;
    };
    Sample.callStaticMethod = function () {
    };
    return Sample;
}());

ES2015 以上

"use strict";
class Sample {
    callMethod() {
    }
    getThis() {
        return this;
    }
    static callStaticMethod() {
    }
}

なお、 ES3 に設定した場合の sample.callMethod.prototype の結果は下記の通りです。

{…}
    constructor: function callMethod()​
    < prototype>: Object {…}

Object.getPrototypeOf(target) と target.prototype

若干今更感ありますが、 Object.getPrototypeOf(target) と target.prototype の違いとは何でしょうか。

Object.getPrototypeOf(target) (古くは proto ) は、 target の内部プロパティである Prototype を取得するものである、と。

内部プロパティというものが今一つ分かっていないのですが、実際の結果を見ると

console.log(sample.callMethod);
console.log(Object.getPrototypeOf(sample.callMethod));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(sample.callMethod)));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(sample.callMethod))));

この結果が

function callMethod()
function ()
Object { … }
null

メソッド > Punction Prototype > Object > null となっていることから、親の Prototype を取得できる、というイメージでしょうか。

では target.prototype は?というところですが、

この辺りをみると、自身( Constructor 関数)が持つ prototype プロパティであるようです。

…うーん、わかるようなわからないような。。。

この辺りはもう少し調べてみることにします。

prototype メソッドの prototype (図)

先ほどの Object.getPrototypeOf(target) を null が出るまで繰り返した結果を図にしておきます。

f:id:mslGt:20190910223456p:plain

static メソッド

次は static メソッドです。

console.log(Sample.callStaticMethod);
console.log(Object.getPrototypeOf(Sample.callStaticMethod));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Sample.callStaticMethod)));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Sample.callStaticMethod))));

結果は。。。

function callStaticMethod()
function ()
Object { … }
null

先ほどとほぼ同じですね。

Sample.callStaticMethod.prototype も同様に undefined になります。

また、 Object.getPrototypeOf(Sample.callStaticMethod)) で得られる Function prototype は prototype method のものと同じです。
(プロパティを追加すると、もう片方でも利用できます。すべきではないと思いますが)

static メソッドの prototype (図)

f:id:mslGt:20190910223525p:plain

関数

では関数だとどうでしょうか。

function callFunction(){   
}
console.log(callFunction);
console.log(Object.getPrototypeOf(callFunction));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(callFunction)));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(callFunction))));
console.log(callFunction.prototype);
console.log(Object.getPrototypeOf(callFunction.prototype));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(callFunction.prototype)));

関数の場合は先ほどまでとは違い、 callFunction.prototype で値を得ることができます。

結果は下記の通り。

function callFunction()
function ()
Object { … }
null
{ … }
    constructor: function callFunction()​
    < prototype>: { … }
Object { … }
null

f:id:mslGt:20190910223610p:plain

Object.getPrototypeOf(target) で辿った結果だけを見ると、 prototype メソッドも static メソッドも関数も大きな違いはなさそうです。

関数だけ target.prototype が正しく取れるのはなぜ?と思いはしますが、メソッドの方は単にクラス構文が登場して、値の設定の仕方が変わったから、というだけのような気もします。

この辺も突っ込んで調べてみたいところ。

クラス

今度はクラスです。

JavaScript のクラスは関数の糖衣構文である。。。ということは。。。?

class Sample{
    public callMethod(){
    }
    public getThis(){
        return this;
    }
    public static callStaticMethod(){
    }
}
console.log(Sample);
console.log(Object.getPrototypeOf(Sample));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Sample)));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Sample))));
console.log(Sample.prototype);
console.log(Object.getPrototypeOf(Sample.prototype));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Sample.prototype)));
function Sample()
function ()
Object { … }
null
{ … }
    callMethod: function callMethod()​
    constructor: function Sample()​
    getThis: function getThis()​
    < prototype>: { … }
Object { … }
null

ほぼ関数と同じですね。

f:id:mslGt:20190910223708p:plain

クラスインスタンス

クラスインスタンスはどうでしょうか。

let sample = new Sample();

console.log(sample);
console.log(Object.getPrototypeOf(sample));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(sample)));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(sample))));

クラスインスタンスは target.prototype を持っていないようです。

結果はこちら。

{}
    < prototype>: Object { … }
{…}​
    callMethod: function callMethod()​
    constructor: function Sample()​
    getThis: function getThis()​
    < prototype>: Object { … }
Object { … }
null

インスタンス自体は Object である、と。

で、 Object.getPrototypeOf(sample) は Sample.prototype と同じ値です。

f:id:mslGt:20190910223738p:plain

マージしてみる

ここまでの図をまとめてみました。

f:id:mslGt:20190910223844p:plain

※オレンジ線は参照の意

こうやって見ると、クラスやクラスインスタンスからはメソッドを参照できていますが、メソッドからはほとんどつながりがなく(大元の Object しかない)、雑に引数としてメソッドを渡すと元のクラスインスタンスを見失ってしまう理由がわかるような気がします(気のせいかも)。

proto が非推奨となったり、動的に親となる Prototype が切り替わる、といった部分を考慮していなかったりと単純化してはいるはずですが、やはり複雑ではありますね。

なお、グローバルオブジェクト( Window )はこんな感じでした。

console.log(this);
console.log(Object.getPrototypeOf(this));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(this)));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this))));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this)))));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this))))));
Window 
WindowPrototype { … }
WindowProperties {  }
EventTargetPrototype { addEventListener: addEventListener(), removeEventListener: removeEventListener(), dispatchEvent: dispatchEvent(), … }
Object { … }
null

f:id:mslGt:20190910223903p:plain

うーむ、ちっとも理解できた気がしない。。。

内部的なところに突っ込むともう少しわかったりするのでしょうか。。。

【TypeScript】Decoratorを覗き見る 2

はじめに

今回は Decorator Factory から見てみますよ。

Decorator Factory

正直ドキュメントを見ても Class Decorator を返す関数、というだけでどう活用できるものなのか今一つ分かっていなかったのですが、これを使うことで、 Decorator に引数を渡すことができるようになります。

decoratorFactorySample.ts

import { Sample } from "../sample";

export function decoratorFactorySample(name: string, id: number, s: Sample){
    return function decoratorSample(constructor: Function){
        console.log("generate id:" + id + " name:" + name + " " + constructor);
    }
}

operationSample.ts

import { decoratorFactorySample } from "./decoratorSamples/decoratorFactorySample";

@decoratorFactorySample("john", 1, {call: () => console.log("hello sample"), message: "goodomorning"})
export class OperationSample{
    public constructor(){
        console.log("constructor death");
    }
    public sayHello(){
        console.log("hello world!");
    }
}

引数の個数や種類に制限は無いようで、上記のように増やしても問題なく実行できました。

逆に引数を 0 にすると。。。

operationSample.ts

import { decoratorFactorySample } from "./decoratorSamples/decoratorFactorySample";

@decoratorFactorySample()
export class OperationSample{
    public constructor(){
        console.log("constructor death");
    }
    public sayHello(){
        console.log("hello world!");
    }
}

おや?これ InversifyJS を利用するコードで見たことあるような。。。
(まだコードを読んでいないので確証はありませんが)

なお当然とも言えますが、この後登場する Method Decorator など Class 以外の Decorator でも使用できます。

Method Decorator

お次は Method Decorator です。

名前の通りメソッドに付与する Decorator ですが、今度は引数が 3 つあります。

methodDecoratorSample.ts

export function methodDecoratorSample(target: any, propertyKey: string, discripter: PropertyDescriptor){
    console.log("method decorator start");
    console.log(target);
    console.log(propertyKey);
    console.log(discripter);    
    console.log("method decorator end");
}

operationSample.ts

import { methodDecoratorSample } from "./decoratorSamples/methodDecoratorSample";

export class OperationSample{
    public constructor(){
        console.log("constructor death");
    }
    @methodDecoratorSample
    public sayHello(){
        console.log("hello world!");
    }
}

実行すると下記のようになります。

method decorator start
Object { sayHello: sayHello(), … }
sayHello
Object { value: sayHello(), writable: true, enumerable: true, configurable: true }
method decorator end

第一引数はメソッドを定義しているクラス、第二引数は対象のメソッド名、第三引数はメソッドの情報、ということのようです。

出力結果を見ると、 PropertyDescriptor.value は対象のメソッドであるため、 PropertyDescriptor.value.call(target) のようにすれば呼び出すことができます。

が、ドキュメントによると target を ES5 より低く設定している場合は PropertyDescriptor が undefined になるようなので、 ES3 に設定している、またはその恐れがある場合は使わない方が良いかもしれません。

なお Mastering TypeScript 3 では第三引数を optional にしていました(現行バージョンとの違いによるものかもしれませんが)。

なお、 PropertyDescriptor.value を使う以外に関数を実行する方法として、下記があります。

target[propertyKey]();

関数の呼び方

ここまで同じ関数(メソッド)を呼ぶのにいくつかの方法が登場しました。

例えばこんなクラスがあったとして

export class Sample{
    private message = "たこやき";
    public callMethod(){
        console.log("I love " + this.message);
        this.message = "チーズ";
    }
    public static callStaticMethod(){
        console.log("I love " + Sample.prototype.message);
        Sample.prototype.message = "コーンポタージュ";
    }
}

これらのメソッドを呼ぶ方法として下記が挙げられます
( 6 、 7 、 8 は 2 、 3 、 4 と同じですが、static メソッドということで追加しました)。

export function doSomething(){
    let sample = new Sample();
    // 1
    sample.callMethod();
    // 2
    sample.callMethod.call(sample);
    // 3
    sample.callMethod.apply(sample);
    // 4
    sample["callMethod"]();
    // -- static method --
    // 5
    Sample.callStaticMethod();
    // 6
    Sample.callStaticMethod.call(Sample);
    // 7
    Sample.callStaticMethod.apply(Sample);
    // 8
    Sample["callStaticMethod"]();
}

1 、5 は C# でもおなじみの方法ですが、他はあまり見慣れない呼び方です。

実行結果は下記の通りです(どれがどの結果かわかるように番号を振っています)。

// 1
I love たこやき
// 2
I love チーズ
// 3
I love チーズ
// 4
I love チーズ
// 5
I love undefined
// 6
I love コーンポタージュ
// 7
I love コーンポタージュ
// 8
I love コーンポタージュ

1 ~ 4 までのインスタンスを作って呼んでいるメソッド( prototype メソッド)は、呼び方はともかく想定通りの動きではあります。

5 は2回繰り返すと、他の static メソッド同様「I love コーンポタージュ」となります。

ということは、プロパティ message の定義時に渡している「たこやき」を受け取ることはできず、 かつ static メソッド間では状態が保持されていることがわかります。

なお最後に 1 を追加するともう一度「I love チーズ」と出力されるため、 prototype メソッド側(というか生成したインスタンス)も static メソッドの状態を見ていないことがわかります。

早速それぞれのメソッドを見ていきたいのですが( prototype も気になる)、その前に(見た目上)なじみのある書き方である 1 、5 について。

実は、 JavaScript(TypeScript) におけるクラス、というのは、関数の糖衣構文であるとのこと。

そのため先ほどのクラスは、 ES3 で変換すると下記のようになります( TypeScript Playground で確認) 。

"use strict"
var Sample = /** @class */ (function () {
    function Sample() {
        this.message = "たこやき";
    }
    Sample.prototype.callMethod = function () {
        console.log("I love " + this.message);
        this.message = "チーズ";
    };
    Sample.callStaticMethod = function () {
        console.log("I love " + Sample.prototype.message);
        Sample.prototype.message = "コーンポタージュ";
    };
    return Sample;
}());

インスタンスを作って呼んでいる callMethod はその名の通り prototype を通して、 static メソッドである callStaticMethod はそのまま Sample につながっています。

いつも通り迷走感がすごいですが、今回は prototype を見てみることにしましょう。

Prototype

上記を見ると、まず JavaScript では、関数(メソッド)・プロパティなどの種類にかかわらず、そのほとんどが prototype という Object を継承していると。

先ほどの実験から prototype メソッドと static メソッドで見ているものが違う、ということはわかりました。

ではそれぞれどう違うのか、とりあえずコンソールに出力して比べてみます。

まずは prototype メソッドから。

mainPage.ts

export function doSomething(){

    let sample = new Sample();

    console.log(sample.callMethod);
    console.log(sample.callMethod.prototype);
}

結果はこちら。

// console.log(sample.callMethod);
callMethod()​
    length: 0
    name: ""
    prototype: {…}
        constructor: function callMethod()​​
        < prototype>: Object { … }
    < prototype>: ()
        apply: function apply()
        arguments: 
        bind: function bind()
        call: function call()
        caller: 
        constructor: function Function()
        length: 0
        name: ""
        toSource: function toSource()
        toString: function toString()
        Symbol(Symbol.hasInstance): function Symbol.hasInstance()
        < get arguments()>: function arguments()
        < set arguments()>: function arguments()
        < get caller()>: function caller()
        < set caller()>: function caller()
        < prototype>: Object { … }

// console.log(sample.callMethod.prototype);
{…}
    constructor: function callMethod()​
    < prototype>: {…}
        __defineGetter__: function __defineGetter__()
        __defineSetter__: function __defineSetter__()
        __lookupGetter__: function __lookupGetter__()
        __lookupSetter__: function __lookupSetter__()
        __proto__: 
        constructor: function Object()
        hasOwnProperty: function hasOwnProperty()
        isPrototypeOf: function isPrototypeOf()
        propertyIsEnumerable: function propertyIsEnumerable()
        toLocaleString: function toLocaleString()
        toSource: function toSource()
        toString: function toString()
        valueOf: function valueOf()
        < get __proto__()>: function __proto__()
        < set __proto__()>: function __proto__()

では static メソッド。

mainPage.ts

export function doSomething(){
    console.log(Sample.callStaticMethod);
    console.log(Sample.callStaticMethod.prototype);
}

結果です。

// console.log(Sample.callStaticMethod);
callStaticMethod()​
    length: 0
    name: ""
    prototype: {…}
        constructor: function callStaticMethod()​​
        < prototype>: Object { … }
    < prototype>: ()
        apply: function apply()
        arguments: 
        bind: function bind()
        call: function call()
        caller: 
        constructor: function Function()
        length: 0
        name: ""
        toSource: function toSource()
        toString: function toString()
        Symbol(Symbol.hasInstance): function Symbol.hasInstance()
        < get arguments()>: function arguments()
        < set arguments()>: function arguments()
        < get caller()>: function caller()
        < set caller()>: function caller()
        < prototype>: Object { … }

// console.log(Sample.callStaticMethod.prototype);
{…}​
    constructor: function callStaticMethod()​
    < prototype>: {…}
        __defineGetter__: function __defineGetter__()
        __defineSetter__: function __defineSetter__()
        __lookupGetter__: function __lookupGetter__()
        __lookupSetter__: function __lookupSetter__()
        __proto__: 
        constructor: function Object()
        hasOwnProperty: function hasOwnProperty()
        isPrototypeOf: function isPrototypeOf()
        propertyIsEnumerable: function propertyIsEnumerable()
        toLocaleString: function toLocaleString()
        toSource: function toSource()
        toString: function toString()
        valueOf: function valueOf()
        < get __proto__()>: function __proto__()
        < set __proto__()>: function __proto__()

うーん。。。 メソッド名の違いを除くとあまり違っているようには見えませんね。。。

見てるところが違っているのかしら。。。

一旦切ります。