JS学習シリーズの目的
このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)
thisとは
「呼び出し元のオブジェクトへの参照を保持するキーワード」
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.nameはpersonオブジェクトのnameプロパティを参照するというわけです。
参照のコピーとthis
thisの基礎が分かったところで、次のようなコードを見てみましょう
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*/
変数refにperson.helloを代入した場合、変数person内にあるhelloメソッドへの参照をコピーします。
この状態でref()を実行すると、変数personを呼び出していないので、thisは呼び出し元のオブジェクトがpersonではなくなります。
この時、thisがどのオブジェクトを参照するのかというと、**グローバルオブジェクト(windowオブジェクト)**を参照します。
ここで非常に重要なことを言いますが、
オブジェクトのメソッドとして実行される場合
'this' => 呼び出し元のオブジェクト
関数として実行される場合
'this' => グローバルオブジェクト
↑のようにthisの参照先は変わります。
したがって
window.name = John;
function a() {
console.log('Hello ' + this.name);
}
a(); /*Hello John*/
上記のようなコードでは、関数a内のthisはグローバルオブジェクトを参照するので、a()を実行した結果は、'Hello John'となります。
コールバック関数とthis
オブジェクトのメソッドをコールバック関数として実行した場合を見てみます
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()を実行した場合は、今までどおりthisはpersonを呼び出し元のオブジェクトとして参照します。
しかし、
関数fnにコールバック関数としてperson.helloを渡した場合、personオブジェクト内のhelloの参照先であるfunctionを変数(引数)に代入しているのと同じことなので、
コールバック関数ref()はperson.helloを参照先とする関数として実行され、thisはグローバルオブジェクトを参照します。
bindとthis
関数として実行するけど、thisの参照先を呼び出し元のオブジェクトにしたい。という時は、'bind'というメソッドを使います。
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*/
上記のコードのように、
変数helloTomにperson.hello.bind(person)と書くと、bindメソッドによってperson.helloのthisの参照先がpersonオブジェクトに固定されます。
したがって、コールバック関数としてfn(helloTom)を実行すると、'Hello Tom'と出力されます。
このように、bindメソッドによるthisの参照先の固定をbindによるthisの束縛と表現します。
また、'bind'は'this'の参照先だけではなく、そのメソッドまたは関数の**'引数'**も固定できます。
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'メソッドは、使用時点で実行します。
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では、使用時点で実行されます。
callとapplyの違いは、固定する引数の指定をする際に、callはbindと同じように**「,」(カンマ)区切りで指定しますが、applyは配列**で指定します。
アロー関数と'this'
今まで、'this'について解説してきました。ここでアロー関数と'this'について解説します。
アロー関数とは、「無名関数を記述しやすくした省略記法」です。
そして結論から言いますが、
「アロー関数内では、'this'という値を保持しません」
では、アロー関数内で'this'を使ったらどうなるか?次のコードを見てみましょう。
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'は初学者が躓きやすいところだと思いますが、しっかり理解しておきましょう!
おまけ
今回の解説では、
オブジェクト内のメソッドを下記のように書きましたが、
const person = {
name: 'Tom',
hello: function() {
console.log('Hello ' + this.name);
}
}
person.hello(); /*Hello Tom*/
下記のように省略した記法もあり、主にこちらの書き方で書かれます。この書き方にも慣れておきましょう。
const person = {
name: 'Tom',
hello() {
console.log('Hello ' + this.name);
}
}
person.hello(); /*Hello Tom*/