Closed10

【JavaScript】this とは何者か

ShionShion

クラス内の this

基本的に、メソッドは object.method() の形で呼び出す。
呼び出されたメソッドの中で、this が何であるかはこのときに決まる

具体的には、. の1つ左のオブジェクトがその中での this になる

下記の例では、this は shion になる。

const shion = new User("shion", 99);
shion.isAdult();

このように、this は同じクラスから生み出される複数のインスタンスオブジェクトがそれぞれ独立し、自分自身のデータを参照できるようにするための仕組みである。

ShionShion

オブジェクト内の this

無論、this はクラス以外でも使用できる。

以下の例では、this をオブジェクトの中で使用している。

const user = {
  name: "shion",
  age: 99,
  isAdult() {
    return this.age >= 20;
  },
};

console.log(user.isAdult()); // true

この場合、. の1つ左のオブジェクトがその中での this になるため、メソッド内の this は user を指すことになる。

ShionShion

アロー関数内の this

結論から言うと、アロー関数は自分自身の this を持たない
自分自身の this が存在しないため、アロー関数では this を外側の関数から受け継ぐことになる。

以下の例では、fn は、object.method() の形で呼ばれていないため、this が何を指しているのかわからず undefined が出力される。
arrowFnは、外側がグローバルスコープなので window オブジェクトが出力される(実行環境がブラウザの場合)。

"use strict";

function fn() {
  console.log(this);
}

const arrowFn = () => {
  console.log(this);
};

fn(); // undefined
arrowFn(); // window
ShionShion

アロー関数内の this をより詳細に

以下を例にする。

class User {
    name: string;
    private age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    public isAdult(): boolean {
        return this.age >= 20;
    }

    public filterOlder(users: readonly User[]) {
        return users.filter(u => u.age > this.age);
    }
}

const shion = new User("shion", 24);
const john = new User("John Smith", 15);
const bob = new User("Bob", 40);

const older = shion.filterOlder([john, bob]);
console.log(older); // [User: { "name": "Bob", "age": 40}] 

u => u.age > this.ageのthisは、fileterOlderメソッド内での this と同じとなる。なぜなら、アロー関数内の this は外側の関数の this を受け継いでおり、今回外側の関数とはfilterOlderだからである。

shion.filterOlder()の形で呼ばれた場合は、メソッド内での this は shion になる。

外側の関数の this を受け継ぐというアロー関数の性質により、u => u.age > this.age というアロー関数の中の this も shion となる。これにより、shion よりも age が大きいものを抽出した配列が返ることになる。

ShionShion

アロー関数を使わない書き方

上記の例をアロー関数を使わない書き方で書くと少し面倒なことが起きる。

class User {
    name: string;
    private age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    public isAdult(): boolean {
        return this.age >= 20;
    }

    public filterOlder(users: readonly User[]) {
        return users.filter(function(this: User, v) {
            return v.age > this.age // Cannot read properties of undefined (reading 'age') 
        });
    }
}

const shion = new User("shion", 24);
const john = new User("John Smith", 15);
const bob = new User("Bob", 40);

const older = shion.filterOlder([john, bob]);
console.log(older);

this が undefined になってしまい、エラーが発生した。
なぜなら、実際にその関数を呼び出すのはusers.filterであり、users.filterは shion のことなど知らないから、users.filter から呼び出されたコールバック関数内で this が shion になることを期待するのは無理筋である。

結局、この関数式内で filterOlder 内の this を参照するためには、あらかじめ this を別の変数に退避しておく必要がある。このような退避用によく使われるのが _this。このようにすれば期待通りに動作する。

public filterOlder(users: readonly User[]) {
    const _this = this;
    return users.filter(function(u) { return u.age > _this.age });
}

とまあ、こんなテクニックは過去のもの。

なぜなら自分自身の this を持たないアロー関数を使えば良いからね。

ShionShion

this を操作するメソッド

関数の中での this を指定しつつ関数呼び出しを行う、apply(call), bind の2つを抑える。

ShionShion

apply

func.apply(obj, args)の形で呼び出すことで、「関数 func を、中での this を obj にして呼び出す」という意味となる。

引数 args は func に与えられる引数全てを配列に入れたもの。例を見た方が早い。

class User {
    name: string;
    private age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    public isAdult(): boolean {
        return this.age >= 20;
    }
}

const shion = new User('shion', 24);
const john = new User('John Smith', 15);

console.log(shion.isAdult()); // true

// shion.isAdult を、john を this として呼び出す
console.log(shion.isAdult.apply(john, [])); // false
ShionShion

call

apply と意味は同じ。

唯一の違いは、引数を第二引数に配列で渡していたのを、call()ではそのまま渡す点。

func.apply(obj, [1, 2, 3]);
func.call(obj, 1, 2, 3);
ShionShion

bind

bind は、this が固定されている新しい関数オブジェクトを作るという効果を持つ。

例えば、func.bind(obj) のようにした場合、返り値として「呼び出し時の this が obj に固定された func 関数」が得られる。こうして得られた関数は、呼び出し方に左右されず常に this が obj になる。

class User {
    name: string;
    private age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    public isAdult(): boolean {
        return this.age >= 20;
    }
}

const shion = new User('shion', 24);
const john = new User('John Smith', 15);

// this が shion に固定された isAdult
const boundIsAdult = shion.isAdult.bind(shion)

console.log(boundIsAdult()) // true
console.log(boundIsAdult.call(john)); // true

boundIsAdult.call(john) としても true が返る点がポイント。

これは呼び出し方に関わらず boundIsAdult() の中では常に this は shion であり、shion.age ≥ 20 の評価結果が返されるからである。

このスクラップは2024/12/18にクローズされました