#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*/