JavaScriptのthisキーワードを解明する

概要: このチュートリアルでは、JavaScriptのthisの値について学習し、さまざまなコンテキストで明確に理解します。

Java、C#、またはPHPなどの他のプログラミング言語を使用したことがある場合は、すでにthisキーワードに精通しているでしょう。

これらの言語では、thisキーワードはクラスの現在のインスタンスを表し、クラス内でのみ意味を持ちます。

JavaScriptにもthisキーワードがあります。ただし、JavaScriptのthisキーワードは、他のプログラミング言語とは異なる動作をします。

JavaScriptでは、グローバルコンテキストと関数コンテキストthisキーワードを使用できます。さらに、thisキーワードの動作は、厳格モードと非厳格モードで異なります。

thisキーワードとは

一般的に、thisは、関数がプロパティであるオブジェクトを参照します。言い換えれば、thisは現在関数を呼び出しているオブジェクトを参照します。

next()メソッドを持つcounterオブジェクトがあるとします。next()メソッドを呼び出すと、thisオブジェクトにアクセスできます。

let counter = {
  count: 0,
  next: function () {
    return ++this.count;
  },
};

counter.next();Code language: JavaScript (javascript)

next()関数内では、thiscounterオブジェクトを参照します。次のメソッド呼び出しを参照してください。

counter.next();Code language: CSS (css)

next()は、counterオブジェクトのプロパティである関数です。したがって、next()関数内では、thiscounterオブジェクトを参照します。

グローバルコンテキスト

グローバルコンテキストでは、thisグローバルオブジェクトを参照します。これは、Webブラウザではwindowオブジェクト、Node.jsではglobalオブジェクトです。

この動作は、厳格モードと非厳格モードの両方で一貫しています。以下は、Webブラウザでの出力です。

console.log(this === window); // trueCode language: JavaScript (javascript)

グローバルコンテキストでthisオブジェクトにプロパティを割り当てると、JavaScriptは次の例に示すように、グローバルオブジェクトにプロパティを追加します。

this.color= 'Red';
console.log(window.color); // 'Red'Code language: JavaScript (javascript)

関数コンテキスト

JavaScriptでは、関数を次の方法で呼び出すことができます。

  • 関数呼び出し
  • メソッド呼び出し
  • コンストラクタ呼び出し
  • 間接呼び出し

各関数呼び出しは、独自のコンテキストを定義します。したがって、thisは異なる動作をします。

1)単純な関数呼び出し

非厳格モードでは、関数が次のように呼び出された場合、thisはグローバルオブジェクトを参照します。

function show() {
   console.log(this === window); // true
}

show();Code language: JavaScript (javascript)

show()関数を呼び出すと、thisグローバルオブジェクトを参照します。これは、Webブラウザではwindow、Node.jsではglobalです。

show()関数を呼び出すことは、次と同じです。

window.show();Code language: JavaScript (javascript)

厳格モードでは、JavaScriptは関数内のthisundefinedに設定します。例:

"use strict";

function show() {
    console.log(this === undefined);
}

show();Code language: JavaScript (javascript)

厳格モードを有効にするには、JavaScriptファイルの先頭で"use strict"ディレクティブを使用します。厳格モードを特定の関数のみに適用する場合は、関数本体の先頭に配置します。

厳格モードはECMAScript 5.1以降で使用可能になっていることに注意してください。strictモードは、関数とネストされた関数の両方に適用されます。例:

function show() {
    "use strict";
    console.log(this === undefined); // true

    function display() {
        console.log(this === undefined); // true
    }
    display();
}

show();Code language: JavaScript (javascript)

出力

true
trueCode language: JavaScript (javascript)

コンソールに示すように、display()内部関数では、thisundefinedに設定されます。

2)メソッド呼び出し

オブジェクトのメソッドを呼び出すと、JavaScriptはthisをメソッドを所有するオブジェクトに設定します。次のcarオブジェクトを参照してください。

let car = {
    brand: 'Honda',
    getBrand: function () {
        return this.brand;
    }
}

console.log(car.getBrand()); // HondaCode language: JavaScript (javascript)

この例では、getBrand()メソッドのthisオブジェクトはcarオブジェクトを参照します。

メソッドは値であるオブジェクトのプロパティであるため、変数に格納できます。

let brand = car.getBrand;Code language: JavaScript (javascript)

次に、変数を介してメソッドを呼び出します。

console.log(brand()); // undefinedCode language: JavaScript (javascript)

オブジェクトを指定せずにメソッドを呼び出すと、JavaScriptは非厳格モードでthisをグローバルオブジェクトに、厳格モードでundefinedに設定するため、"Honda"の代わりにundefinedが返されます。

この問題を解決するには、Function.prototypeオブジェクトのbind()メソッドを使用します。bind()メソッドは、thisキーワードが指定された値に設定された新しい関数を作成します。

let brand = car.getBrand.bind(car);
console.log(brand()); // Honda
Code language: JavaScript (javascript)

この例では、brand()メソッドを呼び出すと、thisキーワードがcarオブジェクトにバインドされます。例:

let car = {
    brand: 'Honda',
    getBrand: function () {
        return this.brand;
    }
}

let bike = {
    brand: 'Harley Davidson'
}

let brand = car.getBrand.bind(bike);
console.log(brand());Code language: JavaScript (javascript)

出力

Harley Davidson

この例では、bind()メソッドがthisbikeオブジェクトに設定するため、コンソールにbikeオブジェクトのbrandプロパティの値が表示されます。

3)コンストラクタ呼び出し

newキーワードを使用して関数オブジェクトのインスタンスを作成する場合、関数をコンストラクタとして使用します。

次の例では、Car関数を宣言し、コンストラクタとして呼び出します。

function Car(brand) {
    this.brand = brand;
}

Car.prototype.getBrand = function () {
    return this.brand;
}

let car = new Car('Honda');
console.log(car.getBrand());Code language: JavaScript (javascript)

new Car('Honda')は、Car関数のコンストラクタ呼び出しです。

JavaScriptは新しいオブジェクトを作成し、thisを新しく作成されたオブジェクトに設定します。このパターンは、1つの潜在的な問題を除いてうまく機能します。

ここで、Car()を関数またはコンストラクタとして呼び出すことができます。次のようにnewキーワードを省略すると、

var bmw = Car('BMW');
console.log(bmw.brand);
// => TypeError: Cannot read property 'brand' of undefinedCode language: JavaScript (javascript)

Car()thisの値がグローバルオブジェクトに設定されるため、bmw.brandundefinedを返します。

Car()関数が常にコンストラクタ呼び出しを使用して呼び出されるようにするには、次のようにCar()関数の先頭にチェックを追加します。

function Car(brand) {
    if (!(this instanceof Car)) {
        throw Error('Must use the new operator to call the function');
    }
    this.brand = brand;
}Code language: JavaScript (javascript)

ES6では、関数が単純な呼び出しとして呼び出されたか、コンストラクタとして呼び出されたかを検出できる、new.targetという名前のメタプロパティが導入されました。

次のように、new.targetメタプロパティを使用するCar()関数を変更できます。

function Car(brand) {
    if (!new.target) {
        throw Error('Must use the new operator to call the function');
    }
    this.brand = brand;
}Code language: JavaScript (javascript)

4)間接呼び出し

JavaScriptでは、関数はファーストクラスの市民です。言い換えれば、関数はFunction型のインスタンスであるオブジェクトです。

Function型には、call()apply()という2つのメソッドがあります。これらのメソッドを使用すると、関数を呼び出すときにthis値を設定できます。例:

function getBrand(prefix) {
    console.log(prefix + this.brand);
}

let honda = {
    brand: 'Honda'
};
let audi = {
    brand: 'Audi'
};

getBrand.call(honda, "It's a ");
getBrand.call(audi, "It's an ");Code language: JavaScript (javascript)

出力

It's a Honda
It's an AudiCode language: PHP (php)

この例では、getBrand()関数のcall()メソッドを使用して、getBrand()関数を間接的に呼び出しました。call()メソッドの最初の引数としてhondaaudiオブジェクトを渡したため、各呼び出しで対応するブランドを取得しました。

apply()メソッドは、2番目の引数が引数の配列である点を除いて、call()メソッドに似ています。

getBrand.apply(honda, ["It's a "]); // "It's a Honda"
getBrand.apply(audi, ["It's an "]); // "It's a Audi"Code language: JavaScript (javascript)

アロー関数

ES6では、アロー関数と呼ばれる新しい概念が導入されました。アロー関数では、JavaScriptはthisを字句的に設定します。

つまり、アロー関数は独自の実行コンテキストを作成せず、アロー関数が定義されている外側の関数からthisを継承します。次の例を参照してください。

let getThis = () => this;
console.log(getThis() === window); // trueCode language: JavaScript (javascript)

この例では、thisの値はグローバルオブジェクト、つまりWebブラウザではwindowに設定されます。

アロー関数は独自の実行コンテキストを作成しないため、アロー関数を使用してメソッドを定義すると問題が発生します。例:

function Car() {
  this.speed = 120;
}

Car.prototype.getSpeed = () => {
  return this.speed;
};

var car = new Car();
console.log(car.getSpeed()); // 👉 undefinedCode language: JavaScript (javascript)

getSpeed()メソッド内では、thisの値はグローバルオブジェクトを参照しますが、Carオブジェクトではありません。ただし、グローバルオブジェクトにはspeedというプロパティがないため、getSpeed()メソッドのthis.speedundefinedを返します。

このチュートリアルは役に立ちましたか?