5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【JS学習その⑦】JavaScriptにおけるthis

Last updated at Posted at 2020-09-25

#JS学習シリーズの目的
このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)

#thisとは
呼び出し元のオブジェクトへの参照を保持するキーワード

main.js
const person = {
    name: 'Tom',
    hello: function() {
        console.log('Hello ' + this.name);
    }
}

person.hello(); /*Hello Tom*/

上記のコードでは、

1.JavaScriptではまず変数personを呼び出す
2.person内のhelloメソッドに参照が向く
3.helloメソッドが参照しているfunctionを実行する

↑の流れになっています。
**thisはこの時、呼び出し元のオブジェクトであるperson**を参照するので、this.namepersonオブジェクトのnameプロパティを参照するというわけです。

#参照のコピーとthis
thisの基礎が分かったところで、次のようなコードを見てみましょう

main.js
window.name = 'John';

const person = {
    name: 'Tom',
    hello: function() {
        console.log('Hello ' + this.name);
    }
}

const ref = person.hello;
ref(); /*Hello John*/

person.hello(); /*Hello Tom*/

変数refperson.helloを代入した場合、変数person内にあるhelloメソッドへの参照をコピーします。
この状態でref()を実行すると、変数personを呼び出していないので、thisは呼び出し元のオブジェクトがpersonではなくなります。
この時、thisがどのオブジェクトを参照するのかというと、**グローバルオブジェクト(windowオブジェクト)**を参照します。

ここで非常に重要なことを言いますが、

オブジェクトのメソッドとして実行される場合
'this' => 呼び出し元のオブジェクト

関数として実行される場合
'this' => グローバルオブジェクト

↑のようにthisの参照先は変わります。

したがって

main.js
window.name = John;

function a() {
    console.log('Hello ' + this.name);
}

a(); /*Hello John*/

上記のようなコードでは、関数a内のthisはグローバルオブジェクトを参照するので、a()を実行した結果は、'Hello John'となります。

#コールバック関数とthis
オブジェクトのメソッドをコールバック関数として実行した場合を見てみます

main.js
window.name = 'John';

const person = {
    name: 'Tom',
    hello: function() {
        console.log('Hello ' + this.name);
    }
}
person.hello(); /*Hello Tom*/

function fn(ref) {
    ref();
}

fn(person.hello); /*Hello John*/

上記のコードのように、
person.hello()を実行した場合は、今までどおりthispersonを呼び出し元のオブジェクトとして参照します。
しかし、
関数fnにコールバック関数としてperson.helloを渡した場合、personオブジェクト内のhelloの参照先であるfunctionを変数(引数)に代入しているのと同じことなので、
コールバック関数ref()person.helloを参照先とする関数として実行され、thisはグローバルオブジェクトを参照します。

#bindとthis
関数として実行するけど、thisの参照先を呼び出し元のオブジェクトにしたい。という時は、'bind'というメソッドを使います。

main.js
window.name = 'John';

const person = {
    name: 'Tom',
    hello: function() {
        console.log('Hello ' + this.name);
    }
}
person.hello(); /*Hello Tom*/

const helloTom = person.hello.bind(person);

function fn(ref) {
    ref();
}

fn(helloTom); /*Hello Tom*/

上記のコードのように、
変数helloTomperson.hello.bind(person)と書くと、bindメソッドによってperson.hellothisの参照先がpersonオブジェクトに固定されます。
したがって、コールバック関数としてfn(helloTom)を実行すると、'Hello Tom'と出力されます。

このように、bindメソッドによるthisの参照先の固定をbindによるthisの束縛と表現します。

また、'bind'は'this'の参照先だけではなく、そのメソッドまたは関数の**'引数'**も固定できます。

main.js
window.name = 'John';

function a(name) {
    console.log('hello ' + name);
}

const b = a.bind(null, 'Tim');

b(); /*hello Tim*/

上記のように書いた場合、bindの第2引数以降に指定したキーワードで関数の引数を固定できます。(※引数の固定は、複数指定できます)
bindの第1引数でthisの参照先を固定しない場合は、nullを指定します。

ここで、重要なメカニズムとしてbindメソッドでは、(今回の例で説明すると)

1.person.helloの参照先であるfunctionが存在する
2.person.hello.bind(person)によってthisの参照先をpersonに固定したfunction'が別のメモリ空間にコピーされる

この、'this'の参照先を固定した別のfunction'がメモリ空間に作成されるというところは大事なのでしっかり理解しておきましょう!

##call,applyと'this'
前述した'bind'のもう一つの特徴として**'bind'の使用時点で実行はしない**という点があります。

ここで紹介する'call'メソッドと'apply'メソッドは、使用時点で実行します

main.js
function a(name, name1) {
    console.log('hello ' + this.name + '' + name + ' ' + name1);
}

const tim = {name: 'Tim'};

const b = a.bind(tim, 'Bob', 'John');

b(); /*hello Tim Bob John*/

a.apply(tim, ['Bob', 'John']); /*hello Tim Bob John*/
a.call(tim, 'Bob', 'John'); /*hello Tim Bob John*/

上記のコードのように、
bindでは使用時点では実行されず、b()のように関数を実行した時点で実行されますが、
call,applyでは、使用時点で実行されます。
callapplyの違いは、固定する引数の指定をする際に、callbindと同じように**「,」(カンマ)区切りで指定しますが、apply配列**で指定します。

#アロー関数と'this'
今まで、'this'について解説してきました。ここでアロー関数と'this'について解説します。
アロー関数とは、「無名関数を記述しやすくした省略記法」です。
そして結論から言いますが、
アロー関数内では、'this'という値を保持しません
では、アロー関数内で'this'を使ったらどうなるか?次のコードを見てみましょう。

main.js
window.name = 'John';

const person = {
    name: 'Tom';
    hello: () => {
        console.log('Hello ' + this.name);
    }
}
person.hello(); /*Hello John*/

上記のコードでは、
person.helloメソッドではアロー関数を使用しています。そして、そのメソッド内でthisという値を使用しています。
前述した通り、アロー関数では'this'という値を保持しません。そこで、'this'はスコープチェーンをたどってレキシカルスコープ(外部スコープ)から値を探します
今回の場合、直近の値はグローバルオブジェクト(windowオブジェクト)内のwindow.nameになります。
よってperson.hello()の値は'Hello John'となります。

#まとめ
いかがでしたでしょうか。
JavaScriptにおいて'this'は初学者が躓きやすいところだと思いますが、しっかり理解しておきましょう!

#おまけ
今回の解説では、
オブジェクト内のメソッドを下記のように書きましたが、

main.js
const person = {
    name: 'Tom',
    hello: function() {
        console.log('Hello ' + this.name);
    }
}

person.hello(); /*Hello Tom*/

下記のように省略した記法もあり、主にこちらの書き方で書かれます。この書き方にも慣れておきましょう。

main.js
const person = {
    name: 'Tom',
    hello() {
        console.log('Hello ' + this.name);
    }
}

person.hello(); /*Hello Tom*/
5
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?