【JavaScript 上級講座】thisのまとめ(5つの例題でthisを完全制覇)
JavaScriptのthisキーワードを学習する。 thisの定義は簡単である。
this プログラムを実行した時のオブジェクトである。
thisは、プログラムを実行した時のオブジェクトを意味する。 thisの定義は、かくの如く簡単である。 それにもかかわらず、thisは多くの問題を提起する。
thisに関わる多くの問題は、「プログラムを実行した時」という部分から発生する。 thisが意味するオブジェクトは、 プログラムを記述した時のオブジェクトではない。
プログラムを記述した時、「thisはこのオブジェクトを意味する」と思っていたが、 いざプログラムを実行すると、オブジェクトが変化していて、 「thisが意味するオブジェクトが想定外だった」という場面が多々有る。
本講座では多種多様のプログラムを見ながら、thisに慣れていく。
thisの基礎
002//display()関数を定義
003function display () {
004
005 //thisを表示
006 alert ( this ) ;
007}
008//[window]
009display () ;
010</script>
プログラムを実行すると「window」と表示する。
3行目でdisplay()関数を定義する。オブジェクトの中に関数を定義しない場合、 自動的にwindowオブジェクト(グローバルオブジェクト)のプロパティになる。 さて、9行目でdisplay()関数を実行する。 display()関数が実行すると、6行目が実行する。
「this」が存在する。このthisは何を意味するだろうか?
this プログラムを実行した時のオブジェクトである。
いま、windowオブジェクトの中でdisplay()関数を実行している。 つまり、thisはwindowオブジェクトを意味する。 プログラムを実行すると「window」と表示する。
このような感じでサンプルプログラムを見る。最初の方は簡単なthisである。
002//ioriオブジェクト
003var iori = {
004 x : "IORI" ,
005 display : function () {
006 alert ( this.x ) ;
007 }
008}
009//[IORI]
010iori.display () ;
011</script>
プログラムを実行すると「IORI」と表示する。
3行目で変数ioriを宣言して、オブジェクトを参照する。 オブジェクトの内容は、後ほど見る。 10行目で変数ioriが参照するオブジェクトのdisplay()関数を実行する。
004 x : "IORI" ,
005 display : function () {
006 alert ( this.x ) ;
007 }
008}
5行目のdisplay()関数が実行する。 6行目にthisがある。このthisは何を意味するだろうか?
this プログラムを実行した時のオブジェクトである。
いま、ioriが参照するオブジェクトに対してdisplay()関数を実行した。 つまり、実行中のオブジェクトは「ioriが参照するオブジェクト」である。 6行目のthisは、「ioriが参照するオブジェクト」を意味する。 オブジェクトのxプロパティの値は文字列「IORI」である。 ダイアログに「IORI」と表示する。解説は以上である。
基礎的thisの最後のサンプルプログラムである。
002//Ioriコンストラクタ関数
003var Iori = function ( x ) {
004 this.x = x ;
005}
006//display()関数を定義
007Iori.prototype.display = function () {
008 alert ( this.x ) ;
009}
010//Iori型オブジェクト
011var iori = new Iori ( "IORI" ) ;
012
013//[IORI]
014iori.display () ;
015</script>
プログラムを実行すると「IORI」と表示する。
3行目でIoriコンストラクタ関数を定義する。 7行目でIoriコンストラクタ関数のprototypeオブジェクトにdisplay()関数を定義する。 11行目でIoriコンストラクタ関数からオブジェクトを生成して、 変数ioriへ代入する。14行目で変数ioriが参照するオブジェクトに対して、 display()関数を実行する。
003var Iori = function ( x ) {
004 this.x = x ;
005}
006//display()関数を定義
007Iori.prototype.display = function () {
008 alert ( this.x ) ;
009}
7行目のdisplay()関数が実行する。8行目にthisがある。 ・・・これは難しい。図解する。
オブジェクト | Iori | |
変数 iori |
x: "IORI" __proto__ : { 【参照X】 } |
prototype : { 【参照X】 display : function __proto__ : { 【参照Y】 } } |
変数ioriが参照するオブジェクトは、Ioriコンストラクタ関数から生成されたため、 xプロパティを持つ。 こんかい、オブジェクトのxプロパティに 文字列「IORI」が入っている。関数は存在しない。
この状態で、オブジェクトに対してdisplay()関数を実行した。 オブジェクトにdisplay()関数は存在しない。 その場合、プロトタイプチェーンをさかのぼる。 Ioriコンストラクタ関数のprototypeオブジェクトにdisplay()関数が存在する。 このdisplay()関数が実行する。 さて、display()関数の中でthisが利用されていた。 いま、動いているオブジェクトは、変数ioriが参照するオブジェクトである。
008 alert ( this.x ) ;
009}
thisは実行中のオブジェクトである。つまりioriオブジェクトである。 8行目を実行すると、つぎのような状態になる。
008 alert ( { x : "IORI" } .x ) ;
009}
プログラムを実行すると、 ダイアログに「IORI」と表示する。 解説は以上である。
ここまでが基礎的サンプルプログラムである。
関数が持つ関数、call()関数を実行する
002//window.getX()関数
003function getX () {
004 alert ( this.x ) ;
005}
006//window.xプロパティ
007var x = "ERROR" ;
008
009//ioriオブジェクト
010var iori = {
011 x : "IORI"
012}
013//[ERROR]
014window.getX.call ( window ) ;
015</script>
プログラムを実行すると「ERROR」と表示する。
3行目でgetX()関数を定義する。getX()関数はグローバルスコープで定義しているので、 windowオブジェクトの関数になる。 7行目で変数xを定義して文字列「ERROR」を代入する。 変数xもwindowオブジェクトのプロパティになる。 10行目から12行目でオブジェクトを定義する。 こんかい、このプログラムは意味がないので気にしないでほしい。
14行目で、・・・14行目は何をしているのかな?
関数.call ( [オブジェクト] ) オブジェクトに対して、関数を実行する。
まず注意したいのが、まず関数が存在する。 そして、関数の中のcall()関数を実行する。 実は、関数自身もオブジェクトである。 そして 関数の中に、様々な関数が存在する。 call()関数も、関数内に存在する関数(メソッド)の1つである。
関数.call ( [オブジェクト] ) オブジェクトに対して、関数を実行する。
引数にオブジェクトを渡してcall()関数を実行すると、 オブジェクトに対して「関数」が実行する。
002//window.getX()関数
003function getX () {
004 alert ( this.x ) ;
005}
006//window.xプロパティ
007var x = "ERROR" ;
008
009//ioriオブジェクト
010var iori = {
011 x : "IORI"
012}
013//[ERROR]
014window.getX.call ( window ) ;
015</script>
14行目で、call()関数を実行する。call()関数の引数に、windowオブジェクトを渡す。 つまり14行目は、 windowオブジェクトに対して、getX()関数を実行する プログラムである。
3行目でgetX()関数が定義されている。14行目が実行すると、 この関数が呼ばれる。4行目のthisは何を意味するだろうか? 今、windowオブジェクトに対してgetX()関数を実行した。 つまり、thisはwindowオブジェクトを意味する。 windowオブジェクトのxプロパティは、7行目の文字列「ERROR」である。 4行目が実行すると、ダイアログに「ERROR」と表示する。 解説は以上である。
次のプログラムで、call()関数の挙動を確認する。
002//window.getX()関数
003function getX () {
004 alert ( this.x ) ;
005}
006//window.xプロパティ
007var x = "ERROR" ;
008
009//ioriオブジェクト
010var iori = {
011 x : "IORI"
012}
013//[IORI]
014window.getX.call ( iori ) ;
015</script>
プログラムを実行すると「IORI」と表示する。
不思議なプログラムである。前回との違いは、14行目である。 14行目でcall()関数を実行する。 call()関数は、getX()関数を実行する関数である。 今回、call()関数の引数に変数ioriが参照するオブジェクトを渡す。 call()関数が実行すると、変数ioriが参照するオブジェクトに対して、 getX()関数が実行する。変数ioriが参照するオブジェクトは、 getX()関数を持っていない。という疑問が高速で飛んでくる。
もう一度おさらいする。いまwindowオブジェクトのgetX()関数を、 ioriオブジェクトに対して実行する。 実行する関数は、window.getX()関数である。 「window.getX()関数は、どのオブジェクトの中に存在するか?」という問いは、 存在しない。存在しない。window.getX()関数が動く。
004 alert ( this.x ) ;
005}
006//window.xプロパティ
007var x = "ERROR" ;
008
009//ioriオブジェクト
010var iori = {
011 x : "IORI"
012}
3行目が動く。4行目のthisは何を意味するだろうか? getX()関数はwindowオブジェクトの関数である。 では、thisはwindowオブジェクトだろうか? windowオブジェクトのxプロパティがダイアログに表示するだろうか?
this プログラムを実行した時のオブジェクトである。
いま、window.getX()関数は変数ioriが参照するオブジェクトに対して動作している。 thisはwindowオブジェクトの中に存在するが、 ここのthisは変数ioriが参照するオブジェクトである。
004 alert ( { x : "IORI" }.x ) ;
005}
006//window.xプロパティ
007var x = "ERROR" ;
008
009//ioriオブジェクト
010var iori = {
011 x : "IORI"
012}
thisをオブジェクトへ切り替えた。プログラムが実行すると「IORI」とダイアログに表示する。 解説は以上である。
関数というものは、 オブジェクトの中に定義するため、 「関数はオブジェクトとセット」と見なしがちである。 あるいは「関数はオブジェクトの中のプロパティと連携する」と考えがちである。 それは普通の考え方であるし、そうありたいものである。
しかし JavaScriptでは、関数は独立して存在する。 「独立した関数を、どのオブジェクトに対して実行するか?」という点が JavaScriptでは重要である。 実行中のオブジェクトがthisである。
「関数は独立したモノ」という認識を明確にするため、 もう一つサンプルプログラムを用意した。
002//itemオブジェクト
003var item = {
004 x : "item" ,
005 getX : function () {
006 alert ( this.x ) ;
007 }
008} ;
009//window.xプロパティ
010var x = "ERROR" ;
011
012//IORIオブジェクト
013var iori = {
014 x : "IORI"
015}
016//[ERROR]
017item.getX.call ( window ) ;
018
019//[IORI]
020item.getX.call ( iori ) ;
021</script>
プログラムを実行すると「ERROR」、「IORI」と表示する。
解説する。3行目で変数itemを宣言してオブジェクトを代入する。 オブジェクトの中を見る。
003var item = {
004 x : "item" ,
005 getX : function () {
006 alert ( this.x ) ;
007 }
008} ;
itemオブジェクトの中にxプロパティとgetX()関数が存在する。 getX()関数の処理を見る。 6行目で、xプロパティの値をダイアログに表示する。 ここのthisは何を意味するだろうか? itemオブジェクトだろうか? もし、thisがitemオブジェクトならば、 getX()関数を実行すると、ダイアログに「item」と表示する。
this プログラムを実行した時のオブジェクトである。
thisは、(別段の処理をしない限り) プログラムを実行した時に決定する。 いまは、itemオブジェクトを意味しているように見えるが、 プログラムを実行するまで分からない。
010var x = "ERROR" ;
011
012//IORIオブジェクト
013var iori = {
014 x : "IORI"
015}
016//[ERROR]
017item.getX.call ( window ) ;
018
019//[IORI]
020item.getX.call ( iori ) ;
解説を続ける。10行目で変数xへ文字列「ERROR」を代入する。 変数xはグローバルスコープで定義しているのでwindowオブジェクトのプロパティになる。 13行目でオブジェクトを定義して、変数ioriへ代入する。 変数ioriが参照するオブジェクトには、xプロパティが存在する。
さて、17行目でcall()関数を実行する。 call()関数の引数にwindowオブジェクトを渡す。 つまり、windowオブジェクトに対してitem.getX()関数を実行する。 実行するオブジェクトはwindowオブジェクトである。
003var item = {
004 x : "item" ,
005 getX : function () {
006 alert ( this.x ) ;
007 }
008} ;
item.getX()関数が実行すると、6行目が動く。 6行目のthisは、今動いているオブジェクトである。 今動いているオブジェクトはwindowオブジェクトである。 itemオブジェクトはgetX()関数を保持するオブジェクトである。 動いているオブジェクトではない。 6行目のプログラムが実行すると、windowオブジェクトのxプロパティの値「ERROR」が ダイアログに表示する。
013var iori = {
014 x : "IORI"
015}
016//[ERROR]
017item.getX.call ( window ) ;
018
019//[IORI]
020item.getX.call ( iori ) ;
20行目で、call()関数を実行する。 call()関数の引数に、変数ioriが参照するオブジェクトを渡す。 つまり、変数ioriが参照するオブジェクトに対して、 itemオブジェクトのgetX()関数を実行する。
004 x : "item" ,
005 getX : function () {
006 alert ( this.x ) ;
007 }
008} ;
009//window.xプロパティ
010var x = "ERROR" ;
011
012//IORIオブジェクト
013var iori = {
014 x : "IORI"
015}
5行目のgetX()関数が実行すると、6行目が動作する。 6行目のthisは何か?現在、ioriオブジェクトに対してgetX()関数が実行している。 つまり、thisはioriが参照するオブジェクトである。 プログラムを実行すると、 ioriオブジェクトのxプロパティの値「IORI」がダイアログに表示する。
これがJavaScriptである。
thisとインナー関数とクロージャ
thisを理解するために学習用のプログラムを掲載する。 今後、利用しなくなるプログラムかもしれない。 しかし、古代のプログラムをメンテナンスする場合、 こんかいのプログラムが役立つこともあるだろう。
002//ioriオブジェクト
003var iori = {
004 x : "IORI" ,
005
006 //関数を戻す
007 factory : function () {
008
009 return function () {
010 return this.x ;
011 }
012 }
013}
014//factory()関数を実行して
015//関数を受け取る
016var f = iori.factory () ;
017
018//[undefined]
019alert ( f () );
020</script>
プログラムを実行すると「undefined」と表示する。
16行目から解説する。16行目でioriオブジェクトのfactory()関数を実行して、 戻り値を変数fで受け取る。 では、factory()関数を見る。
008
009 return function () {
010 return this.x ;
011 }
012 }
factory()関数の処理は、9行目だけである。 9行目でreturnキーワードの後ろに戻り値を記述する。 戻り値は関数である。 factory()関数を実行すると、9行目から11行目までの関数が戻る。
002//ioriオブジェクト
003var iori = {
004 x : "IORI" ,
005
006 //関数を戻す
007 factory : function () {
008
009 return function () {
010 return this.x ;
011 }
012 }
013}
014//factory()関数を実行して
015//関数を受け取る
016var f = iori.factory () ;
017
018//[undefined]
019alert ( f () );
020</script>
いま、16行目を解説していた。16行目でfactory()関数を実行すると、 戻り値として9行目の関数が戻る。
003//関数を受け取る
004var f = function () {
005 return this.x ;
006 }
007
008//[undefined]
009alert ( f () );
変数fに代入する関数を、このように記述できる。 さて、プログラムの最後で、変数fに代入された関数を実行する。
003var iori = {
004 x : "IORI" ,
005
006 //関数を戻す
007 factory : function () {
008
009 return function () {
010 return this.x ;
011 }
012 }
013}
ここで9行目の関数が実行する。10行目にthisがある。 このthisはなんだろうか?一見ioriオブジェクトのような気がする。 しかし、プログラムをもう一度見直す。
015//関数を受け取る
016var f = iori.factory () ;
017
018//[undefined]
019alert ( f () );
変数fはグローバルスコープで宣言しているので、windowオブジェクトのプロパティである。 つまり、プログラムは次のように記述できる。
015//関数を受け取る
016window.f = iori.factory () ;
017
018//[undefined]
019alert ( window.f () );
このプログラムを見ると、windowオブジェクトに対して関数f()を実行している。
003var iori = {
004 x : "IORI" ,
005
006 //関数を戻す
007 factory : function () {
008
009 return function () {
010 return this.x ;
011 }
012 }
013}
関数f()は9行目から11行目の関数である。 10行目のthisは、windowオブジェクトを意味する。 こんかいwindowオブジェクトにxプロパティを作成していないので、 プログラムを実行すると「undefined」と表示する。 グローバルスコープに変数xを宣言して値を代入したなら、 その値が表示する。
002//ioriオブジェクト
003var iori = {
004 x : "IORI" ,
005
006 //関数を戻す
007 factory : function () {
008
009 return function () {
010 return this.x ;
011 }
012 }
013}
014var x = "ERROR" ;
015
016//factory()関数を実行して
017//関数を受け取る
018var f = iori.factory () ;
019
020//[ERROR]
021alert ( f () );
022</script>
プログラムを実行すると「ERROR」と表示する。
14行目でwindowオブジェクトにxプロパティを追加した。 21行目を実行すると、 windowオブジェクトのxプロパティの値「ERROR」がダイアログに表示する。 解説は以上である。
こんかい、プログラムを記述した時、 プログラマーの頭の中では、10行目のthisはioriオブジェクトを意味してほしかったかもしれない。
そうに違いない。そうかもしれない。
プログラムを改善する。 10行目を実行した時に、xプロパティの文字列「IORI」を 戻り値にする方法を見る。
002//ioriオブジェクト
003var iori = {
004 x : "IORI" ,
005
006 //関数を戻す
007 factory : function () {
008
009 //クロージャ
010 var that = this ;
011 return function () {
012 return that.x ;
013 }
014 }
015}
016//factory()関数を実行して
017//関数を受け取る
018var f = iori.factory () ;
019
020//[IORI]
021alert ( f () );
022</script>
プログラムを実行すると「IORI」と表示する。
先ほどのプログラムを改善する方法として、クロージャが広く知られている。
18行目から解説する。18行目でioriオブジェクトのfactory()関数を実行して、 戻り値を変数fへ代入する。
7行目のfactory()関数を解説する。 10行目でthisの値を変数thatへ代入する。 この時点で、thisの値はioriオブジェクトを意味する。 そのオブジェクトを変数thatへ代入する。11行目で関数を戻す。 戻り値の関数で変数thatを利用している。 関数内から関数外の変数を利用する時、 関数と変数はセットで存在する。 つまり、factory()関数の処理終了後も変数thatは存続して、 ioriオブジェクトを参照し続ける。
002//ioriオブジェクト
003var iori = {
004 x : "IORI" ,
005
006 //関数を戻す
007 factory : function () {
008
009 //クロージャ
010 var that = this ;
011 return function () {
012 return that.x ;
013 }
014 }
015}
016//factory()関数を実行して
017//関数を受け取る
018var f = iori.factory () ;
019
020//[IORI]
021alert ( f () );
022</script>
21行目で関数f()を実行する。関数f()はfactory()関数の戻り値である。
002alert (
003 {
004 //クロージャ
005 var that = this ;
006
007 function () {
008 return that.x ;
009 }
010 }
011);
012</script>
関数f()は変数thatとセットになっている。 変数thatには、ioriオブジェクトが入っている。 プログラムを実行すると、ioriオブジェクトのxプロパティの値「IORI」が ダイアログに表示する。 解説は以上である。
配列のforEach()関数とthisの関係
配列のforEach()関数とthisの関係を見る。
002//オブジェクトを作成
003var iori = {
004 //配列を作成
005 names : [ "IORI" , "iori" ] ,
006
007 //prefixを定義
008 prefix : "走る"
009}
010//getNames()関数
011iori.getNames = function () {
012
013 //forEach()関数で全ての要素を取得
014 this.names.forEach (
015
016 function ( name , i , names ) {
017 alert ( this.prefix + name );
018 }
019 ) ;
020}
021//[undefinedIORI][undefinediori]
022iori.getNames();
023</script>
プログラムを実行すると「undefinedIORI」、「undefinediori」と表示する。
プログラムを実行すると「undefined」と表示するので、気をつける必要がある。 解説する。3行目でioriオブジェクトを作成する。 オブジェクトのプロパティはnames配列とprefix文字列である。 11行目でioriオブジェクトにgetNames()関数を追加する。関数の処理は後ほど見る。 22行目で、getNames()関数を実行する。
012
013 //forEach()関数で全ての要素を取得
014 this.names.forEach (
015
016 function ( name , i , names ) {
017 alert ( this.prefix + name );
018 }
019 ) ;
020}
getNames()関数を解説する。14行目でnames配列のforEach()関数を実行する。 14行目のthisはioriオブジェクトである。 問題は、forEach()関数に渡した関数の中のthisである(17行目のthis)。 forEach()関数の引数に渡す関数は、16行目から18行目までである。 17行目のthis.prefixは、一見ioriオブジェクトのprefixプロパティを参照していそうだが、 そうではない。17行目のthisはwindowオブジェクトである。 現在windowオブジェクトにprefixプロパティは存在しないので、 this.prefixの値は「undefined」である。 解説は以上である。
このプログラムを正しく改善する方法をざっと見る。
002//オブジェクトを作成
003var iori = {
004 //配列を作成
005 names : [ "IORI" , "iori" ] ,
006
007 //prefixを定義
008 prefix : "走る"
009}
010//getNames()関数
011iori.getNames = function () {
012
013 //forで全ての要素を取得
014 for ( var i = 0 ; i < this.names.length ; i++ ) {
015 alert ( this.prefix + this.names[i] );
016 }
017}
018//[走るIORI][走るiori]
019iori.getNames();
020</script>
プログラムを実行すると「走るIORI」、「走るiori」と表示する。
forEach()関数を諦めて、通常のfor文で処理する解決方法である。 どうでしょうか?
002//オブジェクトを作成
003var iori = {
004 //配列を作成
005 names : [ "IORI" , "iori" ] ,
006
007 //prefixを定義
008 prefix : "走る"
009}
010//getNames()関数
011iori.getNames = function () {
012
013 var that = this ;
014
015 //forEach()関数で全ての要素を取得
016 this.names.forEach (
017
018 function ( name , i , names ) {
019 alert ( that.prefix + name );
020 }
021 ) ;
022}
023//[走るIORI][走るiori]
024iori.getNames();
025</script>
プログラムを実行すると「走るIORI」、「走るiori」と表示する。
13行目でthisを変数thatへ代入する。 このthatは19行目で利用する。 関数の中の関数でthisを利用すると、thisの挙動は不安定になる。 それならば、thisを利用するのではなく、 代わりに変数thatを参照させる。 変数thatはただの変数である。
プロトタイプチェーンとthis
最後にプロトタイプチェーンとthisの関係を見て、 本講座を閉じる。
002//Itemコンストラクタ関数
003var Item = function ( ) {
004 this.x = "item" ;
005}
006//run()関数
007Item.prototype.run = function ( ) {
008 return this.x ;
009} ;
010//Ioriオブジェクト
011var Iori = function ( ) {
012 this.x = "iori" ;
013}
014//プロトタイプチェーン
015Object.setPrototypeOf (
016 Iori.prototype ,
017 Item.prototype
018) ;
019//Iori型オブジェクト
020const iori = new Iori () ;
021
022//[iori]
023alert ( iori.run () ) ;
024</script>
プログラムを実行すると「iori」と表示する。
解説する。3行目でItemコンストラクタ関数を定義する。 7行目でItemコンストラクタ関数のprototypeオブジェクトにrun()関数を追加する。 11行目でIoriコンストラクタ関数を定義する。 15行目で、Object.setPrototypeOf()関数を実行して、 Itemコンストラクタ関数とIoriコンストラクタ関数の間に、 プロトタイプチェーンを構築する。 20行目でIoriコンストラクタ関数をnewして、変数ioriへ代入する。 ここで、オブジェクトの関係を見る。
オブジェクト | Iori | Item | |
変数 iori |
x: "IORI" __proto__ : { 【参照X】 } |
prototype : { 【参照X】 __proto__ : { 【参照Y】 } } |
prototype : { 【参照Y】 run : function __proto__ : { } } |
このような状態になる。変数ioriが参照するオブジェクトには、 xプロパティが存在する。他のコンストラクタ関数にはxプロパティは存在しない。 newをしなければ、コンストラクタ関数はただの関数である。 newした時に、プロパティが入ったオブジェクトを戻す。
23行目で変数ioriが参照するオブジェクトに対してrun()関数を実行する。 オブジェクトはrun()関数を持っていない。 そのため、プロトタイプチェーンをさかのぼってrun()関数を探す。 Itemコンストラクタ関数のprototypeオブジェクトの中にrun()関数が存在する。 このrun()関数が実行する。
008 return this.x ;
009} ;
run()関数を実行すると、オブジェクトのxプロパティが戻る。 8行目のthisは現在実行中のオブジェクト、「ioriオブジェクト」である。 プログラムを実行すると「iori」とダイアログに表示する。
解説は以上である。
まとめ
this プログラムを実行した時のオブジェクトである。