JavaScript: これが意味することは何ですか。

JavaScript で this の値を把握するのは難しい場合があります。次に、その方法を示します。

JavaScript の this は、多くのジョークの対象となっています。これは、かなり複雑なためです。ただし、この this を扱わないように、デベロッパーがドメイン固有の複雑な処理を行うことも見受けられます。this について不明な点がある場合は、以下の情報がお役に立てば幸いです。これは私の this ガイドです。

最も具体的な状況から、最も具体的でない状況へと説明していきます。この記事は大きな if (…) … else if () … else if (…) … のようなものです。確認しているコードに一致する最初のセクションに直接移動できます。

  1. 関数が矢印関数として定義されている場合
  2. それ以外の場合、関数またはクラスが new で呼び出された
  3. それ以外の場合、関数に「バインドされた」this 値がある場合
  4. それ以外の場合、this が呼び出し時に設定されている場合
  5. それ以外の場合、関数が親オブジェクト(parent.func())を介して呼び出された場合
  6. それ以外の場合、関数または親スコープが厳格モードの場合
  7. それ以外の場合

関数がアロー関数として定義されている場合:

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

この場合、this の値は親スコープの this常に同じになります。

const outerThis = this;

const arrowFunction = () => {
 
// Always logs `true`:
  console
.log(this === outerThis);
};

矢印関数は、this の内部値を変更できないため、外側の this常に同じであるという点で優れています。

その他の例

矢印関数では、bind を使用して this の値を変更できません

// Logs `true` - bound `this` value is ignored:
arrowFunction
.bind({foo: 'bar'})();

矢印関数を使用する場合、call または applythis の値を変更できません

// Logs `true` - called `this` value is ignored:
arrowFunction
.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction
.apply({foo: 'bar'});

アロー関数では、別のオブジェクトのメンバーとして関数を呼び出して this の値を変更できません

const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj
.arrowFunction();

アロー関数では、コンストラクタとして関数を呼び出して this の値を変更することはできません

// TypeError: arrowFunction is not a constructor
new arrowFunction();

「バインドされた」インスタンス メソッド

インスタンス メソッドで、this が常にクラス インスタンスを参照するようにするには、矢印関数とクラス フィールドを使用するのが最善の方法です。

class Whatever {
  someMethod
= () => {
   
// Always the instance of Whatever:
    console
.log(this);
 
};
}

このパターンは、コンポーネント(React コンポーネントやウェブ コンポーネントなど)のイベント リスナーとしてインスタンス メソッドを使用する場合に非常に便利です。

上記は、「this は親スコープの this と同じになる」というルールに違反しているように見えるかもしれませんが、クラス フィールドをコンストラクタで設定するための糖衣構文と考えると、意味がわかりやすくなります。

class Whatever {
  someMethod
= (() => {
   
const outerThis = this;
   
return () => {
     
// Always logs `true`:
      console
.log(this === outerThis);
   
};
 
})();
}

// …is roughly equivalent to:

class Whatever {
  constructor
() {
   
const outerThis = this;
   
this.someMethod = () => {
     
// Always logs `true`:
      console
.log(this === outerThis);
   
};
 
}
}

別のパターンでは、コンストラクタで既存の関数をバインドするか、コンストラクタで関数を割り当てます。なんらかの理由でクラス フィールドを使用できない場合は、コンストラクタで関数を割り当てることをおすすめします。

class Whatever {
  constructor
() {
   
this.someMethod = () => {
     
// …
   
};
 
}
}

それ以外の場合、関数またはクラスが new で呼び出されると、次のように処理されます。

new Whatever();

上記では、thisObject.create(Whatever.prototype) の結果に設定された状態で Whatever(クラスの場合はそのコンストラクタ関数)が呼び出されます。

class MyClass {
  constructor
() {
    console
.log(
     
this.constructor === Object.create(MyClass.prototype).constructor,
   
);
 
}
}

// Logs `true`:
new MyClass();

古いスタイルのコンストラクタについても同様です。

function MyClass() {
  console
.log(
   
this.constructor === Object.create(MyClass.prototype).constructor,
 
);
}

// Logs `true`:
new MyClass();

その他の例

new で呼び出された場合は、bindthis の値を変更できません

const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();

new で呼び出された関数を別のオブジェクトのメンバーとして呼び出して、this の値を変更することはできません

const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();

それ以外の場合、関数に「バインドされた」this 値がある場合:

function someFunction() {
 
return this;
}

const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);

boundFunction が呼び出されるたびに、その this 値が bindboundObject)に渡されるオブジェクトになります。

// Logs `false`:
console
.log(someFunction() === boundObject);
// Logs `true`:
console
.log(boundFunction() === boundObject);

その他の例

バインドされた関数を呼び出すときに、call または applythis の値を変更することはできません

// Logs `true` - called `this` value is ignored:
console
.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console
.log(boundFunction.apply({foo: 'bar'}) === boundObject);

バインドされた関数を呼び出すときに、別のオブジェクトのメンバーとして関数を呼び出して this の値を変更することはできません

const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console
.log(obj.boundFunction() === boundObject);

または、this が呼び出し時に設定されている場合:

function someFunction() {
 
return this;
}

const someObject = {hello: 'world'};

// Logs `true`:
console
.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console
.log(someFunction.apply(someObject) === someObject);

this の値は、call/apply に渡されるオブジェクトです。

残念ながら、this は DOM イベント リスナーなどによって別の値に設定されるため、使用するとコードがわかりにくくなる可能性があります。

すべきでないこと
element.addEventListener('click', function (event) {
 
// Logs `element`, since the DOM spec sets `this` to
 
// the element the handler is attached to.
  console
.log(this);
});

上記のような場合は this を使用しないで、代わりに次のようにします。

すべきこと
element.addEventListener('click', (event) => {
 
// Ideally, grab it from a parent scope:
  console
.log(element);
 
// But if you can't do that, get it from the event object:
  console
.log(event.currentTarget);
});

それ以外の場合、関数が親オブジェクト(parent.func())を介して呼び出された場合は、次のようにします。

const obj = {
  someMethod
() {
   
return this;
 
},
};

// Logs `true`:
console
.log(obj.someMethod() === obj);

この場合、関数は obj のメンバーとして呼び出されるため、thisobj になります。これは呼び出し時に行われるため、親オブジェクトなしで関数を呼び出した場合や、別の親オブジェクトで呼び出した場合、リンクは切断されます。

const {someMethod} = obj;
// Logs `false`:
console
.log(someMethod() === obj);

const anotherObj = {someMethod};
// Logs `false`:
console
.log(anotherObj.someMethod() === obj);
// Logs `true`:
console
.log(anotherObj.someMethod() === anotherObj);

someMethodobj のメンバーとして呼び出されないためsomeMethod() === obj は false です。次のようなことを試したときに、この問題が発生した可能性があります。

const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');

これは、querySelector の実装が独自の this 値を確認し、それが DOM ノードであることを想定しているため、上記の処理によって接続が切断されるためです。上記を正しく実現するには:

const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);

豆知識: すべての API が内部で this を使用するわけではありません。console.log などのコンソール メソッドが変更され、this への参照が回避されるため、logconsole にバインドする必要がなくなりました。

それ以外の場合、関数または親スコープが厳格モードの場合:

function someFunction() {
 
'use strict';
 
return this;
}

// Logs `true`:
console
.log(someFunction() === undefined);

この場合、this の値は未定義です。親スコープが厳格モード(およびすべてのモジュールが厳格モード)の場合、関数で 'use strict' は必要ありません。

それ以外の場合は以下のとおりです。

function someFunction() {
 
return this;
}

// Logs `true`:
console
.log(someFunction() === globalThis);

この場合、this の値は globalThis と同じです。

さて、

これで完了です。this について確認できた情報は以上です。ご不明な点がございましたら、見落としはございませんか?ツイートでお気軽にお問い合わせください。

レビューを実施した Mathias Bynens 氏、Ingvar Stepanyan 氏、Thomas Steiner 氏に感謝いたします。