JavaScriptには、「JavaScript: The Good Parts ―『良いパーツ』によるベストプラクティス」が発売された頃に、時間をかけて取り組んだ記憶があります。一方、TypeScriptは JavaScript のスーパーセットで JavaScript に 静的型付けを追加したという認識はあるものの、なんとなく理解している程度の状態でした。そこで、正月休みを利用して評判の良い「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで (Software Design plus)」(以下、プロを目指す人のためのTypeScript入門)を読んでいます。
現在、「プロを目指す人のためのTypeScript入門」を読み進めており、まだ「第5章 TypeScript のクラス」までですが、以下の感想を持ちました。
この本は JavaScript の知識がなくても理解できる構成になっています。しかしせっかく時間があるので、特に「第5章 TypeScript のクラス」を JavaScript の prototype と比較しながら読むようにしています。
JavaScript の prototype と比較して読んだメモをまとめます。
JavaScript の prototype について prototype
プロパティと constructor
プロパティおよび コンストラクタ関数
1を中心にまとめます。
以下の内容は「開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質」を参考にしています。
Function
コンストラクタ関数のインスタンスです。しかし new 演算子とともに使用されたときにインスタンスを生成するコンストラクタ関数にもなります
コンストラクタ関数
として呼び出されると)関数の this に戻り値であるインスタンスを設定します。詳細は後述する擬似コードを参照してください( ちなみに Function
自身もコンストラクタ関数です)Function
コンストラクタ関数のインスタンスは prototype
プロパティを持ちますprototype
へのリンク __proto__
を持ちます(現在は __proto__
を直接参照することは非推奨であり Object.getPrototypeOf()
を使って取得します)constructor
プロパティを持ちますfunction Foo(x, y) {
// this = new Object(); 返却するインスタン用オブジェクトを作成
this.x = x;
this.y = y;
// return this; return がなくても this( Foo のインスタンス) を返す
}
コンストラクタ関数
は prototype
プロパティを持つArray
や Number
)は、Function
コンストラクタ関数 のインスタンスfunction Hoge() { /* コード */}
は、Function()
コンストラクタ関数 のインスタンスコンストラクタ関数
は prototype
プロパティを持つconsole.log(Array.prototype === Object.getPrototypeOf(['a', 'b'])); // true Array のインスタンスは Array.prototype のリンクを持つ(参照する)
console.log(Object.prototype === Object.getPrototypeOf(Array.prototype)); // true プロトタイプチェーンは Object.prototype までチェイン
console.log(Array.constructor === Function); // コンストラクタ関数は Function のインスタンス
console.log(Object.constructor === Function); // true
console.log(((x:number) => x).constructor === Function); // true
console.log(Function.prototype === Object.getPrototypeOf((x:number) => x)); // true
console.log(typeof Function.prototype); // function
console.log(typeof Object.prototype); // object
クラスについて MDN に以下のように記載されています2。
JS のクラスはプロトタイプに基づいて構築されていますが、一部の構文や意味はクラスに固有です。
出典:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes
クラスとプロトタイプの関係を「プロを目指す人のためのTypeScript入門」に出てくる User
クラスを例に記載してみます。
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
/*
* TypeScript は JavaScipt のスーパセットなので prototype 継承も有効
*/
// new User() でインスタンを作成
console.log(User.prototype === Object.getPrototypeOf(user1)); // true
console.log(user1.constructor === User); // true
// {}でインスタンスを作成 {} は new Object() の糖衣構文とも考えられる
console.log(User.prototype === Object.getPrototypeOf(user2)); // false
console.log(Object.prototype === Object.getPrototypeOf(user2)); // true
console.log(user2.constructor === User); // false
console.log(user2.constructor === Object) // true
クラスのメソッドは protoype のメソッドになる。
class Product {
#name: string;
#price: number;
constructor(name: string, price: number) {
this.#name = name;
this.#price = price;
}
getOrder(num: number): string {
return `${this.#name} is ${this.#price * num}`
}
}
const product1 = new Product('product1', 100);
const product2 = new Product('product2', 200);
console.log(typeof product1.getOrder, typeof product2.getOrder); // ture
console.log(product1.getOrder === product2.getOrder); // true
console.log(product1.getOrder === Product.prototype.getOrder); // true
console.log(product2.getOrder === Product.prototype.getOrder); // true
「プロを目指す人のためのTypeScript入門」( p221 )でメソッドについて以下の言及があります。
同じクラスの複数のインスタンスがある場合、それらが持つメソッドは同じ関数オブジェクトです。
この同じ関数オブジェクトは prototype に定義されています。
class Product {
#name: string;
#price: number;
constructor(name: string, price: number) {
this.#name = name;
this.#price = price;
}
getOrder(num: number): string {
return `${this.#name} is ${this.#price * num}`
}
}
const product1 = new Product('product1', 1000);
const product2 = new Product('product2', 2000);
console.log(typeof product1.getOrder, typeof product2.getOrder); // function, function
console.log(product1.getOrder === product2.getOrder); // true
console.log(product1.getOrder === Product.prototype.getOrder); // true
console.log(product2.getOrder === Product.prototype.getOrder); // true
// メソッドは prototype ベースなので Production.prototype.getOrder を Function.prototype.apply() で呼び出すこともできる
console.log(Product.prototype.getOrder.apply(product1, [2])); // 2000
console.log(typeof product1.getOrder); // function
// なので Function.prototype.apply() を使用できる
console.log(product1.getOrder.apply(product2, [2])); // this に production2 を指定しているので 4000 になる
prototype の例として TypeScript クラスを ES 5 にトランスパイルしてみます。
// typescript
class Foo {
public x: number;
public echo: (x:number) => number;
constructor(x:number) {
this.x = x;
// インスタンスメソッドを定義
this.echo = x => x
};
// プロトタイプベースのメソッド定義
double(): number {
return this.x**2
}
}
const foo1 = new Foo(3);
foo1.double();
ts.config の target
を es5 にしてトランスパイルされたコードです。
"use strict";
// JavaScript
var Foo = /** @class */ (function () {
function Foo(x) {
this.x = x;
// インスタンスメソッドを定義
this.echo = function (x) { return x; };
}
;
// プロトタイプベースのメソッド定義
Foo.prototype.double = function () {
return Math.pow(this.x, 2);
};
return Foo;
}());
var foo1 = new Foo(3);
foo1.double();
※ コードの インスタンスメソッド
の記載は一般にそのようによばれてかわかりませんが、コンストラクタ関数を使用したクラス宣言でインスタンスメソッド( prototype ではなくインスタンスに定義)がありました。
No comments yet.
改行と段落タグは自動で挿入されます。
メールアドレスは表示されません。