【JavaScript】 thisは本当に単純なものだった
更新日:2020/03/03
ツイートJavaScriptでプログラムを組むとき、thisはとても重要なキーワードです。
ですがサンプルコードなどを参考にして、ただなんとなくで使っている人も多いのではないでしょうか。
そこで今回はJavaScriptのthisについて、独断と偏見で解説していきます。
■お願い
去年ECMAScript2020を頑張って日本語訳しましたが、誰も見てくれません・・・
誰かみて!!
【JavaScript】 学習のためECMAScript2020を日本語訳してみました
thisの例
JavaScriptでプログラムしていて、thisを全く見たことがないという人はあまりいないと思います。
ですが念のため「thisとは?」の話の前に、簡単にthisの使用例をお伝えしておきます。
thisの例
function a( ){
this.param1 = "hello";
this.param2 = "World!";
this.hello = function () {
alert(this.param1 + " " + this.param2);
}
}
let c = new a;
c.hello();
コンストラクタaを呼び出して、オブジェクトcを作成しています。
この説明を読んで、

aは関数では?
という疑問をもった方は、次の記事を見てください。
参考:【JavaScript】 コンストラクターとは?関数とは違うのか?
次に作成したオブジェクトのhello()メソッドを実行します。
するとブラウザ上に、次のようなアラートが表示されました。
オブジェクトが持っているプロパティ(データ)を、メソッドが参照していますね。
つまり、thisは関数やメソッドが持っている独自のデータです。
ウソを書きました。
関数やメソッドは独自のデータなんて持っていません。※注1
thisは単なる変数です。
どういうことなのか?
次項から解説していきます。
※注1 関数そのものを実行するための内部的な独自データは持っています。
thisとは?
thisはオブジェクト
thisはオブジェクトです。
「どんな?」「なんの?」という疑問は意味がありません。
あらゆるオブジェクトがthisになります。

いいすぎでは?
そんなことないですよ。
ホントですよ。
次の例をみてください。
function a( ){
console.log( this );
}
a( ); // 非strictモード: window | strictモード: undefined
a.call( { x:1 , y:2 } ); // Object { x: 1, y: 2 }
a.call( 1 ); // Number { 1 }
a.call( "test" ); // String { "test" }
a.call( function test() { // function test()
this.x = 1;
} );
call()は、関数にthisを与えて実行するメソッドです。
参考:【JavaScript】 そろそろcall()とapply()を理解してみようと思う
数値やテキストなどのプリミティブは、対応するNumberオブジェクトやStringオブジェクトに変換されてthisにセットされています。
さらに関数までもが、thisとして使用できます。
次のようにすると、thisをnewして、メソッドを呼び出すこともできます。
thisをnewしてメソッド呼び出し
function a( ){
let b = new this;
b.bb();
}
function b() {
this.message = "hello";
}
b.prototype.bb = function(){
console.log( this.message );
};
a.call( b );

おー!
これは何かに使えそうですね!
コードが難読化するので、やめてね。
素直に引数で渡そうね。
それはともかく、このようにthisはオブジェクトであり、決まった形がありません。
関数やメソッドはthisを持っていない
関数やメソッドはthisを持っていません。
またウソを書きました。
関数やメソッドは、初期値がセットされたthisしか持っていません。
thisの初期値は、非strictモードならwindowオブジェクト。
strictモードならundefinedです。
関数宣言や関数式で定義された関数オブジェクトを普通に呼び出すと、これらの値がセットされます。
thisの初期値
function a( ){ // または const a = function () {
console.log( this );
}
a( ); // 非strictモード: window | strictモード: undefined
ではメソッドはどうなるのでしょうか?
オブジェクトからメソッドを呼び出す
function a( ){
this.message = "hello";
this.hello = function () {
console.log( this, this.message );
};
}
let b = new a; // オブジェクト作成
b.hello(); // Object { message: "hello", hello: hello() } hello
コンストラクタからオブジェクトを作成しメソッドを呼び出すと、thisには呼び出し元のオブジェクトがセットされます。
メソッドはthisを持っていない
let c = b.hello; // オブジェクトからメソッド取り出し
c(); // window undefined ← thisが初期値に!!
しかしオブジェクトのメソッドを変数にセットして実行すると、thisが初期値になってしまいました。
これは、thisが外部から渡されなかったので初期値のままだった、ということです。
メソッドは自分独自のthisを持っていないので、渡されなければセットできないのです。
暗黙のthis渡し
オブジェクトからオブジェクトが持っているメソッドを呼び出すと、暗黙的にthisにオブジェクトがセットされます。
なんのこっちゃですね。
次のコードは、二つのことを行っています。
b.hello( );
① オブジェクトb の持つhello()メソッドを実行
② hello( ) メソッドのthisに、オブジェクトb をセット(暗黙的にthisにオブジェクトをセット)
この二つは、切り離して考える必要があります。
thisのセットは、call()やapply()を使用することができます。
むしろ、call()やapply()が使用されなかったから、暗黙的にthisをセットしているともいえます。
hello()メソッドの this が オブジェクトb なのは、当然のことではありません。
『暗黙的に渡されているからthisはオブジェクトb』だということを認識しておきましょう。
メソッドはオブジェクトを操作しているだけ
thisが特別なものという認識は、なかなか抜けませんね。
次の例を見てください。
const a1 = {
hello : function () {
console.log( "hello" );
},
func1 : function(){
this.hello();
},
};
const a2 = {
hello : function () {
console.log( "hello Yamada!" );
}
};
const a3 = {
hello : function () {
console.log( "hello Hanako!" );
}
};
a1.func1(); // hello
a1.func1.call( a2 ); // hello Yamada!
a1.func1.call( a3 ); // hello Hanako!
ここでは2種類のメソッドが定義されています。
helloメソッドは、文字列をコンソールに出力するだけ。
func1メソッドは、helloメソッドを呼び出しています。
a1.func1()を実行すると"hello"と表示されます。
次に同じa1.func1()に対して、callメソッドでthisをa2オブジェクトへ変更します。
すると、"hello Yamada!"と表示されます。
同じようにa3オブジェクトへ変更すると、今度は"hello Hanako!"と表示されます。
これは、どんな意味があるのでしょうか?
a1.func1()は、自分がどのオブジェクトに所属しているかどうかなんて、全く気にしていないということです。
メソッドと自身が所属するオブジェクトには関連性がなく、メソッドはただ単に与えられたthisを操作しているだけです。
次の例のように、オブジェクトを引数で渡して操作しているのと同じです。
let a1 = {
hello : function () { console.log( "hello" ); },
func1 : function( obj ){
obj.hello();
},
}
a1.func1( a1 ); // hello
a1.func1( a2 ); // hello Yamada!
a1.func1( a3 ); // hello Hanako!
thisは特別なものではないですよね?
this独自の制約
thisはオブジェクトだとお伝えしましたが、実際には他のオブジェクトにない制約があります。
↓↓↓↓
thisは読み込み専用のため、上書きできない。
プロパティの追加や値の変更はできます。
this.prop = 123; // OK
しかしthisそのものに、値をセットすることができません。
this = { x:1 , y:2 }; // SyntaxError: invalid assignment left-hand side
エラーになります。
関数のthis
関数でthisを使うとき、注意する点があります。
次は、僕がよくやる失敗例です。
function a(){
this.x = 1;
console.log( this ); // window
}
a();
thisにプロパティをセットしていろいろ処理しようかなー、と思っていたのに

新しいオブジェクトじゃなくてwindowオブジェクトになっちゃったよ?
xはどこ行ったのー????
追加したプロパティが行方不明になってしまうのです。
ここまで解説したように、thisの初期値がwindowなのが原因です。
this.x = 1は、window.x = 1 だったのです。
ちなみに、strictモードはthisがundefinedになります。
strictモードのthis
"use strict";
function a(){
this.x = 1; // TypeError: this is undefined
console.log( this );
}
a();
undefinedにプロパティを追加しようとすると、エラーで止まってしまうのです。
strict、非strictともに、コードをそのまま使いたいならcall()でthisを与えてあげる必要があります。
call()でthisをセット
function a(){
this.x = 1;
console.log( this ); // Object { x: 1 }
}
a.call( { } );

なるほどー
こうすればいいんですねー
そもそもnewを使用するつもりがない関数で、thisを使うなという話です。
アロー関数のthis
アロー関数は、thisを持っていません。
初期値うんぬん、ではなくて存在自体否定しています。
アロー関数のthis
console.log( this ); // window ←①
const a = () => {
console.log( this ); // window ←これは①を参照している
};
a();
上の例では、アロー関数内の this は window と表示されます。

this持ってますよ?
ところがどっこい、この this は関数の外がわの this なのです。
次の例を見ると、わかると思います。
const b = function () {
this.x = 100;
};
b.prototype.b2 = function(){
console.log( this ); // Object { x: 100 }←①
const a = () =>{
console.log( this ); // Object { x: 100 }←これは①を参照している
};
a( );
};
let c = new b;
c.b2();
オブジェクトc から、プロトタイプで定義されたメソッドb2を実行しています。
この時のthisは、オブジェクトc です。
メソッド内で定義しているアロー関数が、外側のthis①と同じものになっていますね。
これはアロー関数がthisを持っていないので、関数の外側にthisがないかどうか探しにいくためです。
JavaScriptの基本的な考え方なので、よくわからない人は次の記事を参考にしてください。
参考:【JavaScript】 必須知識?スコープとレキシカル環境
ちなみに、アロー関数からfunction定義に変えると windowオブジェクトになります。
const a = function(){
console.log( this ); // window
};
外側のthisを使いたいときは、bind()を使用しないといけません。
const a = function(){
console.log( this ); // Object { x: 100 }
}.bind( this );

bindめんどくさいです…
アロー関数ができたのも、めんどくさいのが一つの理由だったりします。
ちなみにここで紹介しているアロー関数は、関数の外というイメージを強くしたかったので { } を使用しており、少し冗長です。
const a = () =>{ console.log( this ); };
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ こう書ける!!
const a = () => console.log( this );
詳しくは次の記事を参考にしてみてください。
まとめ
ようするに、thisはメソッドに与えられた変数の一つということ。
メソッドは他の変数と同じように、thisを操作しているにすぎない。
メソッドが所属しているオブジェクトとの関連とか、一切関係ない。
ただし、オブジェクトから「 . 」でメソッドを呼ぶと、暗黙的にオブジェクトをthisとしてメソッドに渡してくれる。
これが、メソッドが所属しているオブジェクトと this が、何らかの関係があるような錯覚を感じさせている理由なのかもしれないですね。
記事の内容について

説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
ご意見はこちら。
https://note.affi-sapo-sv.com/info.php
【お願い】

■このページのURL
■このページのタイトル
■リンクタグ