【TypeScript】prototype を追いかける
はじめに
- 【TypeScript】InversifyJSでIoC(DI)体験 1
- 【TypeScript】InversifyJSでIoC(DI)体験 2
- 【TypeScript】Decoratorを覗き見る 1
- 【TypeScript】Decoratorを覗き見る 2
続きです。
が、 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() { } }
class S{
— 千(Masui Masanori) (@masanori_msl) September 8, 2019
public do(){ }
}
let s = new S();
console.log(https://t.co/8VrucUfTvB.prototype);
実行すると[object Object]になる時とundefinedになる時があって、なんでぞ?と思ってたら、ES2015未満かそれ以上かで変わるのね。
あ~びっくりした。 #TypeScripthttps://t.co/Zi1VPQt52S
あ、そうか。JavaScriptのクラスは関数の糖衣構文とはいえ、内部的にES3のコードと同じようなものになる訳じゃない、ということなんかな?
— 千(Masui Masanori) (@masanori_msl) September 8, 2019
どうにも先にコンパイルしてランタイムが実行、みたいな発想から抜けられん(´・ω・`)
なお、 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 は?というところですが、
- Object のプロトタイプ - Web 開発を学ぶ | MDN
- Object.prototype - JavaScript | MDN
- Function.prototype - JavaScript | MDN
この辺りをみると、自身( Constructor 関数)が持つ prototype プロパティであるようです。
…うーん、わかるようなわからないような。。。
この辺りはもう少し調べてみることにします。
prototype メソッドの prototype (図)
先ほどの Object.getPrototypeOf(target) を null が出るまで繰り返した結果を図にしておきます。
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 (図)
関数
では関数だとどうでしょうか。
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
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
ほぼ関数と同じですね。
クラスインスタンス
クラスインスタンスはどうでしょうか。
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 と同じ値です。
マージしてみる
ここまでの図をまとめてみました。
※オレンジ線は参照の意
こうやって見ると、クラスやクラスインスタンスからはメソッドを参照できていますが、メソッドからはほとんどつながりがなく(大元の 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
うーむ、ちっとも理解できた気がしない。。。
内部的なところに突っ込むともう少しわかったりするのでしょうか。。。