「JavaScript 第7版」を読む(8章~9章:関数とクラス)
前回のスクラップの続き
アロー関数の例
const arr = [1, 2, 3, 4, 5];
console.log(arr.map(x => { return x * x; }));
// [ 1, 4, 9, 16, 25 ]
console.log(arr.reduce((acc, elem) => { return acc + elem; }));
// 15 (= 1 + 2 + 3 + 4 + 5)
const arr = [1, 2, 3, 4, 5];
console.log(arr.map(x => x * x));
// [ 1, 4, 9, 16, 25 ]
console.log(arr.reduce((acc, elem) => acc + elem));
// 15 (= 1 + 2 + 3 + 4 + 5)
次の例に注意。オブジェクトリテラルだけを書くときには丸かっこが必要。丸かっこがないと value:
がラベルとして解釈されてしまう。
const f1 = x => {value: x};
const f2 = x => ({value: x});
console.log(f1(2)); // undefined
console.log(f2(2)); // { value: 2 }
関数はオブジェクトの一種なので、任意のプロパティを持つことができる。任意のプロパティはC言語でいう静的変数(static変数)として使える。
function counter() {
counter.count = counter.count ? counter.count + 1 : 1;
return counter.count;
}
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
typeof f
は "function"
を返す。
Object.create
でクラス定義とインスタンス生成
const x = Object.create({
say() {
console.log(`Hello, ${this.name}!`);
},
});
x.name = "World";
console.log(x); // { name: 'World' }
x.say(); // Hello, World!
prototype
を使ったクラス定義とインスタンス生成。関数をコンストラクタとして使っている。
function Person(name) {
this.name = name; // ここのthisはnewされた新しいオブジェクト
}
Person.prototype = {
say() {
console.log(`Hello, ${this.name}!`);
},
};
const x = new Person("World");
console.log(x); // { name: 'World' }
x.say(); // Hello, World!
この2つは同じことをしている。
instanceof
演算子はprototypeが同じオブジェクトかどうかで判定される。
function Person(name) {
this.name = name;
}
Person.prototype = {
say() {
console.log(`Hello, ${this.name}!`);
},
};
const x = new Person("World");
function Person2(name) {
this.name = name;
}
Person2.prototype = Person.prototype;
function Person3(name) {
this.name = name;
}
Person3.prototype = {
say: Person.prototype.say,
}
console.log(x instanceof Person); // true (x の prototype が Person.prototype と同一)
console.log(x instanceof Person2); // true (x の prototype が Person2.prototype と同一)
console.log(x instanceof Person3); // false (x の prototype が Person3.prototype とは異なる)
コンストラクタF
にはF.prototype.constructor
が定義されており、その関数自身が設定されている。
関数とコンストラクタに区別がないので、すべての関数にはデフォルトでF.prototype.constructor
が設定されている。
const F = function() {}
console.log(F.prototype.constructor == F); // true
ES6で導入されたclass構文によるクラス定義とインスタンス生成
class Person {
constructor(name) {
this.name = name; // ここのthisはnewされた新しいオブジェクト
}
say() {
console.log(`Hello, ${this.name}!`);
}
};
const x = new Person("World");
console.log(x); // Person { name: 'World' }
x.say(); // Hello, World!
console.log(x instanceof Person); // true
prototype
を使ったクラス定義とインスタンス生成とは、構文が違うだけで中身の動きは同じ糖衣構文(シンタックスシュガー)と考えればよい、と本には書いてあった。ただconsole.log
の出力にはclass構文のときだけクラス名が付与されるようだ。
class構文は関数宣言と異なり、巻き上げがない。宣言より前では使えない。
静的メソッド(staticメソッド、クラスメソッド)
class Foo {
static bar(a) {
...;
}
}
クラス定義の中にgetterやsetterを、オブジェクトリテラルと同様の書き方で定義できる。
メンバーの値の初期化をコンストラクタの外で行うことも可能だが、コンストラクタの引数に依存する場合はコンストラクタの中に書く必要がある。その場合もメンバーの宣言だけは外に書ける。
#
で始まる名前のメンバーはプライベートメンバーになる。プライベートメンバーはクラス定義の中で宣言が必要。
class Person {
#name; // プライベートフィールド
constructor(name) {
this.#name = name;
}
say() {
console.log(`Hello, ${this.name}!`);
}
get name() {
return this.#name;
}
set name(value) {
this.#name = value;
}
};
const x = new Person("World");
console.log(x); // Person {}
x.say(); // Hello, World!
クラス定義の中に静的変数(static変数)も宣言できる。
class Person {
name;
static count = 0;
constructor(name) {
this.name = name;
Person.count++;
}
};
const x = new Person("Foo");
console.log(Person.count); // 1
const y = new Person("Bar");
console.log(Person.count); // 2
クラスのプロパティであり、C言語やJavaのようにインスタンス経由でx.count
のようにはアクセスできない。
クラスの継承
class SubClass extends SuperClass {
...
}
これはclass構文を使わない場合に以下とだいたい同じ
SubClass.prototype = Object.create(SuperClass.prototype);
コンストラクタの中から親クラス(基底クラス)のコンストラクタを呼ぶにはsuper
を使う。super
を呼ぶまではthis
を使えない。
class SubClass extends SuperClass {
constructor(a) {
...;
super(a);
// これ以降でthisを使える
...;
}
}
メソッドをオーバーライドした場合も親クラスの同名メソッドをsuper
で呼び出せる。
class SubClass extends SuperClass {
foo(a) {
...;
super.foo(a);
...;
}
}