Closed15

「JavaScript 第7版」を読む(8章~9章:関数とクラス)

suzuki-navisuzuki-navi

アロー関数の例

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)
suzuki-navisuzuki-navi

次の例に注意。オブジェクトリテラルだけを書くときには丸かっこが必要。丸かっこがないと value: がラベルとして解釈されてしまう。

const f1 = x => {value: x};
const f2 = x => ({value: x});

console.log(f1(2)); // undefined
console.log(f2(2)); // { value: 2 }
suzuki-navisuzuki-navi

関数はオブジェクトの一種なので、任意のプロパティを持つことができる。任意のプロパティは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
suzuki-navisuzuki-navi

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つは同じことをしている。

suzuki-navisuzuki-navi

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 とは異なる)
suzuki-navisuzuki-navi

コンストラクタFにはF.prototype.constructorが定義されており、その関数自身が設定されている。

関数とコンストラクタに区別がないので、すべての関数にはデフォルトでF.prototype.constructorが設定されている。

const F = function() {}
console.log(F.prototype.constructor == F); // true
suzuki-navisuzuki-navi

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構文のときだけクラス名が付与されるようだ。

suzuki-navisuzuki-navi

class構文は関数宣言と異なり、巻き上げがない。宣言より前では使えない。

suzuki-navisuzuki-navi

静的メソッド(staticメソッド、クラスメソッド)

class Foo {
    static bar(a) {
        ...;
    }
}
suzuki-navisuzuki-navi

クラス定義の中に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!
suzuki-navisuzuki-navi

クラス定義の中に静的変数(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のようにはアクセスできない。

suzuki-navisuzuki-navi

クラスの継承

class SubClass extends SuperClass {
    ...
}

これはclass構文を使わない場合に以下とだいたい同じ

SubClass.prototype = Object.create(SuperClass.prototype);
suzuki-navisuzuki-navi

コンストラクタの中から親クラス(基底クラス)のコンストラクタを呼ぶにはsuperを使う。superを呼ぶまではthisを使えない。

class SubClass extends SuperClass {
    constructor(a) {
        ...;
        super(a);
        // これ以降でthisを使える
        ...;
    }
}

メソッドをオーバーライドした場合も親クラスの同名メソッドをsuperで呼び出せる。

class SubClass extends SuperClass {
    foo(a) {
        ...;
        super.foo(a);
        ...;
    }
}
このスクラップは2023/07/16にクローズされました