Deep TypeScript
![]()
prototype
JavaScriptはプロトタイプベースの言語。別のオブジェクト(=プロトタイプ)を参照して性質を引き継ぐ。 あるオブジェクトにプロパティやメソッドが見つからなければ、プロトタイプをたどって探す。 prototypeに定義された関数はインスタンス間で共有され、メモリ効率も良い。
サンプル
class構文の実態はprototype構文である。
class構文
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name} barks!`);
}
}
prototype構文
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + " makes a sound");
};
// Animal 概念
/*
[[Prototype]]: Function.prototype,
[[Call]]: (thisArg, [name]) => {
// Animal("Pochi")
thisArg.name = name;
return undefined;
},
[[Construct]]: ([name], newTarget) => {
// new Animal("Pochi")
const obj = OrdinaryCreateFromConstructor(newTarget, Animal.prototype);
Animal.[[Call]](obj, [name]);
return obj;
},
prototype: {
[[Prototype]]: Object.prototype,
constructor: Animal,
speak: function() {
console.log(this.name + " makes a sound");
}
},
name: "Animal",
length: 1 // 仮引数の数(name)
*/
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + " barks!");
};
// Dog 概念
/*
[[Prototype]]: Function.prototype,
[[Call]]: (thisArg, [name, breed]) => {
// Dog("Pochi", "Shiba")
Animal.[[Call]](thisArg, [name]);
thisArg.breed = breed;
return undefined;
},
[[Construct]]: ([name, breed], newTarget) => {
// new Dog("Pochi", "Shiba")
obj = OrdinaryCreateFromConstructor(newTarget, "%Object.prototype%", internalProto = Dog.prototype);
Dog.[[Call]](obj, [name, breed]);
return obj;
},
prototype: {
[[Prototype]]: Animal.prototype,
constructor: Dog,
bark: function() {
console.log(this.name + " barks!");
}
},
name: "Dog",
length: 2 // 仮引数の数(name, breed)
*/
const dog = new Dog("Pochi", "Shiba");
dog.speak(); // Pochi makes a sound
dog.bark(); // Pochi barks!
// dog 概念
/*
[[Prototype]]: Dog.prototype,
name: "Pochi",
breed: "Shiba"
*/
// dog 全体像
/*
{
[[Prototype]]: {
[[Prototype]]: {
[[Prototype]]: Object.prototype,
constructor: Animal,
speak: [Function: speak]
},
constructor: Dog,
bark: [Function: bark]
},
name: "Pochi",
breed: "Shiba"
};
*/
Object.getPrototypeOf
prototypeを取得する。
インスタンスオブジェクト
Object.getPrototypeOf(dog) === Dog.prototype; // true
Object.getPrototypeOf(Dog.prototype) === Animal.prototype; // true
Object.getPrototypeOf(Animal.prototype) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype) === null; // true
コンストラクタオブジェクト
Object.getPrototypeOf(Dog) === Function.prototype; // true
Object.getPrototypeOf(Function.prototype) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype) === null; // true
instanceof
インスタンスオブジェクトが特定のコンストラクタオブジェクトから生成されたかどうかを判定する演算子。
サンプル
dog instanceof Dog; // true
dog instanceof Animal; // true
dog instanceof Object; // true
内部処理イメージ
プロトタイプチェーンをたどって判定。
function instanceOf(left, right) {
const proto = right?.prototype;
let cur = Object.getPrototypeOf(left);
while (cur) {
if (cur === proto) return true;
cur = Object.getPrototypeOf(cur);
}
return false;
}
this
種類
デフォルトバインディング
this → strict: undefined / 非strict: window(または global)
function showMessage() {
console.log(this?.message || "no this");
}
const message = "global";
showMessage(); // strict: no this / 非strict: global
暗黙的バインディング
this → 呼び出し元のオブジェクト
const user = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
user.greet(); // "Hello, I'm Alice"
明示的バインディング
this → call / apply / bind の第1引数
function introduce() {
console.log(`I'm ${this.name}, ${this.age} years old.`);
}
const person = { name: "Bob", age: 25 };
introduce.call(person); // "I'm Bob, 25 years old."
コンストラクタ呼び出し(暗黙的バインディングの一種)
this → 新しく生成されたオブジェクト
※以下の例では increment がインスタンスごとに生成されるため、メモリ効率が悪い。通常は Counter.prototype に定義する
function Counter() {
this.count = 0;
this.increment = function() {
this.count++;
console.log(this.count);
};
}
const c = new Counter();
c.increment(); // 1
// c 概念
/*
[[Prototype]]: Counter.prototype,
count: 0,
increment: function() {
this.count++;
console.log(this.count);
}
*/
// c 全体像
/*
[[Prototype]]: {
[[Prototype]]: Object.prototype,
constructor: Counter,
},
count: 0,
increment: function() {
this.count++;
console.log(this.count);
}
*/
クラスのインスタンスメソッド(暗黙的バインディングの一種)
this → そのクラスのインスタンス
※詳しい仕組みは prototype や Array 参照のこと
class Player {
constructor(name: string) {
this.name = name;
this.hp = 100;
}
attack() {
this.hp -= 10;
console.log(`${this.name} attacked! HP: ${this.hp}`);
}
}
const p = new Player("Rin");
p.attack(); // "Rin attacked! HP: 90"
アロー関数(レキシカル this)
this → 定義されたときの外側スコープの this
const team = {
name: "Red",
members: ["Ken", "Mia"],
showMembers() {
this.members.forEach(member => {
console.log(`${member} from ${this.name}`);
});
}
};
team.showMembers();
// "Ken from Red", "Mia from Red"
DOM イベントハンドラ
this → イベントを受け取った要素
document.querySelector("button").addEventListener("click", function() {
this.textContent = "Clicked!";
console.log(this); // <button>Clicked!</button>
});
Array
JavaScriptのArrayはオブジェクトであり、ブラケット記法で要素にアクセスする。
pushやpopはArrayオブジェクトのメソッドであり、暗黙的バインディングによりthisは配列オブジェクトを指す。
const arr = [10, 20, 30];
arr.push(40);
console.log(arr[3]); // 40
// Array 概念
/*
[[Prototype]]: Array.prototype,
0: 10,
1: 20,
2: 30,
length: 3,
*/
// Array 全体像
/*
[[Prototype]]: {
push: function(...items) {
let len = this.length >>> 0;
for (let i = 0; i < items.length; i++) {
this[len] = items[i];
len++;
}
this.length = len;
return len;
},
pop: function() {
let len = this.length >>> 0;
len--;
const value = this[len];
delete this[len];
this.length = len;
return value;
}
},
0: 10,
1: 20,
2: 30,
length: 3,
*/
closure
外部の変数情報を保持した関数のこと。 外部の変数情報はレキシカル環境に保持される。
function createAdder(a: number) {
return function add(b: number) {
const sum = a + b;
return sum;
}
}
// createAdder 概念
/*
[[Prototype]]: Function.prototype,
[[Call]]: (thisArg, [a]) => {
// createAdder(2)
const add = function add(b) {
const sum = a + b;
return sum;
};
return add;
},
prototype: { constructor: createAdder },
name: "createAdder",
length: 1
*/
const addTo2 = createAdder(2);
addTo2(5); // 7
// addTo2 概念
/*
[[Prototype]]: Function.prototype,
[[Call]]: (thisArg, [b]) => {
// addTo2(5)
const sum = [[Environment]].a + b; // 2 + 5
return sum;
},
[[Environment]]: {
// 実行時のレキシカル環境
a: 2, // 環境レコード
outer: GlobalEnv // 外側のレキシカル環境への参照
},
name: "add",
length: 1
*/
asynchronous
Promise
「未来の結果を入れる箱」を作る。
resolve() が呼ばれると「成功」状態に、 reject() が呼ばれると「失敗」状態になる。
const promise = new Promise((resolve, reject) => {
if (Math.random() < 0.5) resolve("成功しました");
else reject("失敗しました");
});
promise
.then(result => console.log(result))
.catch(error => console.error(error));
setTimeout
「一定時間後に関数を実行する予約」を行う。 呼び出し時点で関数はまだ実行されず、タイマーを設定して即座に戻る(ノンブロッキング)。
setTimeout(() => {
console.log("1秒後に実行されました");
}, 1000);
clearTimeout
setTimeout の返す「タイマーID」を clearTimeout に渡して呼ぶと、その予約はキャンセルされる。
const timerId = setTimeout(() => {
console.log("これは実行されません");
}, 3000);
clearTimeout(timerId);
AbortController
controller.signal
スイッチの状態を監視するオブジェクト。
controller.abort()
中断スイッチ。 signal.aborted が true になり、 "abort" イベントが発火する。
const controller = new AbortController();
const { signal } = controller;
signal.addEventListener("abort", () => {
console.log("中断されました!");
});
setTimeout(() => {
controller.abort();
}, 2000);