thisの挙動は非常に分かりにくいです。 階層やイベントリスナ、オブジェクト、アロー関数、jQuery、環境、書く場所などによっても異なります。 それぞれの場合に2階層下までのthisの中身を一覧にして一目で分かるようにしました。 bind, call, applyも分かりやすまとめました。
thisとは
- Javascriptに用意されている特殊なオブジェクトの事。
- thisオブジェクトと呼ぶ。
- thisオブジェクトは呼び出した場所や方法によって何にでもなれる。
- 例外もあるが通常.(ドット)の前についているオブジェクトの事。
- 例外もあるが通常.(ドット)省略形の場合はglobalオブジェクトの事。
- function/constructor関数・メソッドは同列のオブジェクトをthisとするが、呼び出し元の関数のthisは引き継がない。
- function関数・メソッドは、イベントリスナ内では同列のオブジェクトをthisとせず、呼び出された要素がthisとなる。 同列のオブジェクトをthisとするには、そのオブジェクト名でbindしなければならない。
- arrow関数・メソッドはthisの結び付けを行わず、呼び出し元の関数のthisを引き継ぐだけで、自ら同列のオブジェクトをthisとしない。
前置き
- 以下のコードは注釈が無い限り、.jsファイルからGoogle Chromeブラウザで実行しています。
- イベントリスナとjQueryの<script>タグ内の実行もGoogle Chromeブラウザで実行しています。
globalの場所では
- 以下の例は全てnon-strictモードでブラウザで実行の場合なので、globalオブジェクトはWindow
- 以下の場合thisはもちろんglobalオブジェクトなのでWindow。
- 関数やclassの外で変数nameを設定しているが、本来はwindow.nameのところ、globalは省略できるとの申し合わせなので、nameだけで使える。 なので、この場合、this===windowでname===this.name==window.name
- .が付いている前の部分なのでwindowがthis。
var name='ichiri'; console.log(this); //-> [object Window] console.log(this === window); //-> true console.log(name); //-> ichiri console.log(this.name); //-> ichiri console.log(window.name); //-> ichiri console.log(this===window); //-> true
モードや環境でthisの挙動は変わる
ブラウザの場合
- グローバルのthisはglobalオブジェクトで[object window]。
- non-strictモードで、globalオブジェクトは[object window]。
- strictモードで、globalオブジェクトはundefined。
//non-strictモードの場合 console.log(this); //-> [object window] function func1(){ console.log(this); //-> [object window] } func1();
//structモードの場合 'use strict'; // コードの先頭に書かなければ有効にならない。 console.log(this); //-> [object window] function func2(){ console.log(this); //-> undefined } func2();
nodeの場合
- グローバルのthisは空白{}。
- non-strictモードで、globalオブジェクトは[object global]。
- strictモードで、globalオブジェクトはundefined。
//non-strictモードの場合 console.log(this); //-> {} function func1(){ console.log(this); //-> [object global] } func1();
//structモードの場合 'use strict'; // コードの先頭に書かなければ有効にならない。 console.log(this); //-> {} function func2(){ console.log(this); //-> undefined } func2();
functionをglobalに直接書いて実行の場合
- 関数を直接呼び出したらfunctionでもarrowでもその下の層もthisは全てglobalオブジェクト。
- function直接もvarの場合は、thisは.の前のglobalオブジェクト。
- しかしfunctionをconstやletの場合は、.の前のオブジェクトが無いが、globalオブジェクトになる。[object window]はglobalオブジェクト。
- non-strictモード、ブラウザ実行の場合
const func1 = function(){ console.log(this); //-> [object Window] const func2 = function(){console.log(this);} //-> [object Window] const func3 = ()=>{console.log(this);} //-> [object Window] func2(); func3() } func1(); const func4 = function(){ console.log(this); //-> [object Window] const func5 = function(){console.log(this);} //-> [object Window] const func6 = ()=>{console.log(this);} //-> [object Window] func5(); func6() } func4(); var func7 = function(){ console.log(this); //-> [object Window] } func7(); //-> [object Window] this.func7(); //-> [object Window] console.log(func7===this.func7); //-> true console.log(func7===window.func7); //-> true this.func1(); //-> Uncaught TypeError: this.func1 is not a function // varで定義した時だけglobalのプロパティとしてwindowに追加される。 constやletではTypeErrorになる。
オブジェクト内の場合
- オブジェクトobj内直下のfunc1 function内でthisはobjオブジェクトとなる。 この場合、obj.func1()で呼び出しており、thisは.の前のobjなので、thisが.の前のオブジェクトと言うのは当てはまる。
- func1の下のfunc2のfunction内はfunc1のobjを引き継がずglobalオブジェクトとなるが、func3のarrow内だと上のfunc1のfunction内のobjオブジェクトを引き継ぐ。
- 上記func1と同じ層のfunc4のarrow内は、objを引き継ぐ関数が上位に無いので、thisはglobalオブジェクトとなる。 この場合、obj.func4()で呼び出しており、.の前のオブジェクトはobjだがthisはobjではないので注意が必要。
const obj = { name:'ichiri', func1: function(){ console.log(this); //-> {name: 'ichiri', func1: ƒ, func4: ƒ} var func2 = function(){console.log(this);} //-> [object Window] var func3 = ()=>{console.log(this);} //-> {name: 'ichiri', func1: ƒ, func4: ƒ} func2(); func3(); }, func4: ()=>{ console.log(this); //-> [object Window] var func5 = function(){console.log(this);} //-> [object Window] var func6 = ()=>{console.log(this);} //-> [object Window] func5(); func6(); } } obj.func1(); obj.func4();
注意
オブジェクト内のメソッドの場合thisはそのオブジェクトとなるが、そのメソッドだけを受け渡すとthisはglobalオブジェクトとなる。
const obj = { name:'ichiri', func1: function(){ console.log(this); } } obj.func1(); //-> {name: 'ichiri'} const func2 = obj.func1; func2(); //-> [object window]
constructor関数の場合
- オブジェクトのfunctionと同じ。
- constructor関数はfunction()で定義する必要あり。 constructorとしてarrow ()=>{}で定義できない。
const func1 = function(name){ this.name=name; console.log(this); //-> func1 {name: 'apple'} const func2 = function(){console.log(this);} //-> [object Window] const func3 = ()=>{console.log(this);} //-> func1 {name: 'apple'} func2(); func3(); } const fruits =new func1('apple');
classの場合
- .classの場合もオブジェクトと同じ構造。 但し、thisはobjectでなくclassのインスタンスになる事と、globalオブジェクトの代わりがundefinedとなる。
- classの全ての部分はstrictモードと決まっている為、undefinedとなる。
- この次の一覧表を見ると分かりやすいです。
class Class1{ constructor(name){ this.name=name; console.log(this); //-> Class1 {name: 'apple'} const func4 = function(){console.log(this);} //-> undefined const func5 = ()=>{console.log(this);} //-> Class1 {name: 'apple'} func4(); func5(); } func1(){ console.log(this); //-> Class1 {name: 'apple'} const func2 = function(){console.log(this);} //-> undefined const func3 = ()=>{console.log(this);} //-> Class1 {name: 'apple'} func2(); func3(); } } const fruits =new Class1('apple'); fruits.func1();
jQuery内の場合
- jQuery内のthisはファイル読込んでブラウザ実行した場合、jQuery function直下のみターゲットのタグ要素となる。
- <script></script>内との挙動が異なる。
$('h1').on('click', function(){ //<--ここをarrowでも試した---(0) console.log(this); //---(1) const obj = { name:'ichiri', func1: function(){ console.log(this); //---(2) var func2 = function(){console.log(this);} //---(3) var func3 = ()=>{console.log(this);} //---(4) func2(); func3(); } } obj.func1(); var func4 = function(){console.log(this);} //---(5) func4(); })
<script>内記述時のthis
function *
- [object window]
- obj
- [object window]
- obj
- [object window]
arrow
- [object window]
- obj
- [object window]
- obj
- [object window]
*の時のthisの挙動がイベントリスナと異なる。
.jsファイル内記述時のthis
function
- <h1> タグ要素
- obj
- [object window]
- obj
- [object window]
arrow
- [object window]
- obj
- [object window]
- obj
- [object window]
イベントリスナ―内の場合
- イベントリスナ内はjQueryと少し異なりのthisはファイル読込ん場合も<script>内でブラウザ実行した場合の両方、イベントリスナ内 function直下のみターゲットのタグ要素となる。
- <script></script>内との挙動が異なる。
var ele = document.getElementById('post-758') ele.addEventListener('click',()=>{ //<--ここをfunctionでも試した---(0) console.log(this); //---(1) const obj = { name:'ichiri', func1: function(){ console.log(this); //---(2) var func2 = function(){console.log(this);} //---(3) var func3 = ()=>{console.log(this);} //---(4) func2(); func3(); } } obj.func1(); var func4 = function(){console.log(this);} //---(5) func4(); },false);
<script>内記述時のthis
function *
- <>タグ要素
- obj
- [object window]
- obj
- [object window]
arrow
- [object window]
- obj
- [object window]
- obj
- [object window]
.jsファイル内記述時のthis
function
- <>タグ要素
- obj
- [object window]
- obj
- [object window]
arrow
- [object window]
- obj
- [object window]
- obj
- [object window]
*の時のthisの挙動がjQueryと異なる。
直書き、オブジェクト内、constructor内、class内のthis一覧
- 上記の例のまとめです。
- non strictモードでブラウザで実行しています。
- 上記の例ではfunction内に1階層しか実行していませんが、2階層も試してみました。
- functionはfunction関数内のthis。 arrowはarrow関数内のthis。 constructorはconstructor関数内のthis。の事です。
- 一番左から右に向かって下位の階層(Nesting)でのthisの内容を表している。
- この表を見るとarrowは、上位からobjを引き継ぎますが、同列のobjを取り込めないのですね。
- 逆にfunctionは、上位からobjを引き継げないのですが、同列のobjを取り込めるのですね。
- class内は全てstrictモードと決まっているので、globalオブジェクトのところのthisはundefinedになっています。
thisの場所 | 左の1階層下 thisの場所 thisの中身 | 更に左の1階層下 thisの場所 thisの中身 | 更に左の1階層下 thisの場所 thisの中身 |
---|---|---|---|
直書き | function Global | function Global | function Global |
arrow Global | |||
arrow Global | function Global | ||
arrow Global | |||
arrow Global | function Global | function Global | |
function Global | |||
arrow Global | arrow Global | ||
arrow Global | |||
オブジェクトリテラル内 | function オブジェクト | function Global | function Global |
arrow Global | |||
arrow オブジェクト | function Global | ||
arrow オブジェクト | |||
arrow Global | function Global | function Global | |
arrow Global | |||
arrow Global | function Global | ||
arrow Global | |||
constructor 関数内 | constructor オブジェクト | function Global | function Global |
arrow Global | |||
arrow オブジェクト | function Global | ||
arrow オブジェクト | |||
class内 | constructor オブジェクト | function undefined | function undefined |
arrow undefined | |||
arrow オブジェクト | function undefined | ||
arrow オブジェクト | |||
function オブジェクト | function undefined | function undefined | |
arrow undefined | |||
arrow オブジェクト | function undefined | ||
arrow オブジェクト |
call
callはオブジェクトbが持っていないメソッドを、thisをオブジェクトbとして他のオブジェクトaが持っているメソッドを呼び出す(call)ためのもの。 一時的に借りて呼び出すだけなので、他オブジェクトを生成せず、オブジェクトbはオブジェクトのaのメソッドを持っていない。
function a(){console.log(this);} var b = {name:'ichiri'}; a(); //-> [object Window] this はGlobal object。 a.call(b); //-> {name:'ichiri'} bオブジェクトがthisになった。
- オブジェクトa関数内のthisは直接の親オブジェクトa。
- オブジェクトa関数内の関数のthisは直接の親オブジェクトが無いのでグローバルオブジェクト。
- 関数内の関数のthisをオブジェクトaにする場合は、callでオブジェクトaを渡す。
- 外部メソッドを使用する際も、callを使えばオブジェクトaを渡すことが出来る。
- 別の方法はthisをthatとして受け渡す。
var a = { name:'ichiri', func1: function() { console.log(this); //-> {name:'ichiri',func1: f } a object function func2() { console.log(this); //関数内の関数のthisはGlobalオブジェクト } func2(); //-> [object Window] Global object func2.call(this); //-> {name:'ichiri',func1: f } a object var that = this; function func3() { console.log('func3',that); } func3(); //-> {name:'ichiri',func1: f } a object } } a.func1()
apply
- callと同じで引数を配列渡しになっているだけ。
var price = '200'; var obj = {price: '100'}; function func1(color, fruit) { console.log(`色が${color}の${fruit}は${this.price}円です。`); } func1('赤', 'リンゴ'); //-> 色が赤のリンゴは200円です。 func1.call(obj, '赤', 'リンゴ'); //-> 色が赤のリンゴは100円です。 func1.apply(obj, ['赤', 'リンゴ']); //-> 色が赤のリンゴは100円です。 let myArray =['赤', 'リンゴ']; func1.apply(obj, myArray); //-> 色が赤のリンゴは100円です。
因みに、strictモードの場合、thisはundefinedなので、func1()で実行したら、this.priceはundefinedになります。(this.priceでなく、priceなら200を受取れますが…)
bind
基本
- bindはオブジェクトobjをthisとして他のfunc1オブジェクトに結び付け(bind)て、第三のfunc2オブジェクトを生成するもの。
- func1内のthisは[object window]なので、this.nameはundefinedとなる。 しかし、objをbindしてfunc2を作ったので、func2内のthisはobjで、this.nameはappleとなる。
function func1(a,b){ console.log(`${this.name} is ${a+b} yen.`); console.log(a + b) } var obj= { name: "apple" }; func1(10,20); //-> undefined is 30 yen. const func2 = func1.bind(obj); func2(10,20); //-> apple is 30 yen.
bindは一回しか機能しない。
function func1() { return this.a; } var func2 = func1.bind({a: '123'}); var func3 = func2.bind({a: '456'}); var obj = {a: 10, b: func1, c: func2, d: func3}; //直接の場合、globaオブジェクトから別のオブジェクトのbind console.log(func1()); //-> undefined console.log(func2()); //-> 123 console.log(func3()); //-> 123 //オブジェクト内の場合、オブジェクトobjから別のオブジェクトのbind console.log(obj.a); //-> 10 console.log(obj.b()); //-> 10 console.log(obj.c()); //-> 123 console.log(obj.d()); //-> 123
jQueryでのbind
var obj = {name:'apple'}; $('h1').on('click', function(){ console.log(this); //-> thisはobjになる }.bind(obj))
これはエラー
jQueryの場合、functino(){}に直接bindできたのですが、普通のfunctionの宣言文では直接bindできません。 jQueryのfunction()はきっと宣言せずにすぐ実行できる無名即時関数だからでしょうね。
function func1(){ console.log(this); }.bind({name:'ichiri'}) // Uncaught SyntaxError: Unexpected token '.'
おまけ
メインのコードがnon strictモードでも関数やメソッド内だけstrictである可能性もある。
var a = function(){ 'use strict'; console.log(this); } a(); //-> undefined window.a(); //-> [object Window] this.a(); //-> [object Window]
コメント