【JavaScript入門】thisと実行コンテキスト

実行コンテキスト

実行コンテキストとはコード実行時、JavaScriptエンジンによって準備される実行環境を指します。実行コンテキストには、どのような状態でコードが実行されるかの情報(変数、関数、オブジェクト、this参照先など)が保持され、コードが実行される前に必ず生成されます。

実行コンテキストは生成のタイミングで、主に2種類存在します。

グローバルコンテキスト HTMLのscriptタグ直下、またはJavaScriptファイル直下に記述されたコードが実行される直前。
関数コンテキスト 関数が実行される直前。

グローバルコンテキスト

グローバルコンテキストとは、JavaScriptのコードが実行される際に最初に生成されるコンテキストのことを指します(HTMLのscriptタグ直下、またはJavaScriptファイル直下に記述されたコードが実行される直前に生成)。

グローバルコンテキストは、ブラウザのウィンドウ全体に対するグローバルスコープを持ち、ウィンドウ全体で共有される変数や関数を定義できます。グローバルスコープで定義された変数や関数は、どのスコープからでもアクセスすることができます。

  • グローバルコンテキスト内で定義した変数や関数

  • this

  • window

Windowオブジェクト

Windowオブジェクトは、ブラウザ環境においてグローバルオブジェクトとして機能する特別なオブジェクトです。Windowオブジェクトはブラウザのウィンドウ全体を表し、ブラウザのウィンドウに関連する機能やプロパティを提供します。例えば、ブラウザのウィンドウのサイズや位置情報、alert()やconfirm()といったダイアログを表示するためのメソッド、documentオブジェクトを通じてHTML要素にアクセスするためのメソッドやプロパティなどです。

グローバルコンテキストでは、自動的にWindowオブジェクトが生成され、グローバルスコープにはWindowオブジェクトのプロパティやメソッドが定義されます。

Windowオブジェクトは、通常は明示的に指定しなくてもグローバルスコープで定義された変数や関数には、自動的にWindowオブジェクトが補完されます。例えば、以下のコードは同じ結果を返します。

 var foo = "bar";
 console.log(foo); // "bar"
 console.log(window.foo); // "bar"

ただし、厳格モード(use strict)では、明示的にWindowオブジェクトを参照する必要があります。

JavaScriptのグローバルコンテキストは、ブラウザのウィンドウ全体におけるグローバルスコープを提供し、Windowオブジェクトは、ブラウザのウィンドウ全体を表すグローバルオブジェクトとして機能します。

this

thisキーワードは、所属する実行コンテキストにより参照先が変化します。グローバルコンテキストのthisはグローバルオブジェクト(Windowオブジェクト)への参照を表します。

グローバルスコープで以下のようなコードを実行した場合、thisはWindowオブジェクトを参照します。

 // グローバルスコープ内ではthisはWindowオブジェクトを参照する
 console.log(this === window); // true
 console.log(this.innerWidth); // ブラウザのウィンドウの幅を取得

関数コンテキスト

関数コンテキストは、関数の実行時に生成されます。それぞれの関数に対して新しいコンテキストが生成されるため、関数が独自のスコープを持つことになります。

関数コンテキストは、同コンテキスト内で宣言した変数や関数、レキシカルスコープの変数やthis、argumentsオブジェクト、super等、特殊なキーワードも使用可能。

  • 関数コンテキスト内で宣言した変数や関数

  • レキシカルスコープの変数や関数

  • this

  • arguments

  • super

関数コンテキスト内からWindowオブジェクトを参照するには、window.windowと記述することで取得可能です。

 console.log(window.window === window);
 // 出力: true

コールスタック

通常、グローバル、関数コンテキストはコードを全て実行し終わったタイミングで削除されます。しかし、実行コンテキスト内で関数が呼び出されると、元のコンテキストの上に新たな関数コンテキストが積み上がります。この実行コンテキストの積み重ねをコールスタック(または実行コンテキストスタック)と呼びます。

入れることをプッシュ(push)、取り出すことをポップ(pop)と言います。JavaScriptでは関数が呼び出されると、その関数の実行コンテキストがコールスタックにプッシュされ、関数が終了するとコンテキストがポップされます。これにより、関数の呼び出しや戻りの順序が管理されます。

以下は、JavaScriptのコールスタックに関する例を含むコードです。

 function foo() {
   console.log('foo 関数の実行');
   bar();
 }
 
 function bar() {
   console.log('bar 関数の実行');
   baz();
 }
 
 function baz() {
   console.log('baz 関数の実行');
 }
 
 foo(); // foo 関数の実行 -> bar 関数の実行 -> baz 関数の実行

上記コードは、まずグローバルコンテキストが生成されています。そして、foo()が実行された時点で関数コンテキストがコールスタックにプッシュ(グローバルコンテキストの上)されます。そのfoo()内でbar()が実行されるため、新たな関数コンテキストがまたプッシュ(foo関数コンテキストの上)され、さらにbaz()が実行されてもう一つ関数コンテキストがプッシュ(bar関数コンテキストの上)されます。

15行目でfoo()を実行した時、foo()→bar()→baz()の順に実行(プッシュ)され、baz()→bar()→foo()の順に削除(ポップ)されていきます。

関数コンテキストとthis

関数コンテキスト内のthisは、関数の実行の仕方により参照先が異なるため注意が必要です。

関数として実行した場合

thisはグローバルオブジェクト(Windowオブジェクト)を参照します。

 console.log(this); // > Window
 
 function foo() {
   console.log(this);
 }
 
 foo();
 // 出力: Window

ただし、Strictモードが有効の場合、thisはundefinedになります。

オブジェクトのメソッドとして実行した場合

thisはそのメソッドを呼び出したオブジェクトを参照します。

 const obj = {
   prop: 'value',
   foo: function() {
     console.log("値:" + this.prop);
   }
 };
 
 obj.foo();
 // 出力: 値:value

アロー関数内で実行した場合

アロー関数は自身ではthisを持ちません。アロー関数内でthisが使われるとスコープチェーンを辿り、レキシカルスコープにthisを探しに行きます。そして最初に見付かったthisが参照先になります。

 const taro = {
   name: "太郎",
   greet: function() {
       const hanako = {
           name: "花子",
           greet: () => {
               console.log("私の名前は" + this.name + "です。");
           }
       };
       
       hanako.greet();
   }
 };
 
 taro.greet();
 // 出力: 私の名前は太郎です。

上記の例では、taroオブジェクトの中にhanakoオブジェクトを定義しています。taroオブジェクトは無名関数、hanakoオブジェクトはアロー関数を定義しています。15行目でtaroオブジェクトの無名関数を呼び出すと、その中のhanako.greet()が実行されます。hanako.greet()内にはthisを使った文字列の表示がありますが、アロー関数はthisを保持しないため、スコープを遡ってtaro.greet()のthisを使用します。このthisはtaroオブジェクトを参照しているため、出力結果が「私の名前は太郎です。」となるわけです。

コールバック関数でのthisの挙動

 window.name = "二郎";
 
 const ichiro = {
     name: "一郎",
     greet: function() {
         console.log("私の名前は" + this.name + "です。");
     }
 }
 
 function greeting(callback) {
     callback(); // コールバック関数を実行
 }
 
 greeting(ichiro.greet); // コールバック関数としてichiro.greetを渡す
 // 出力: 私の名前は二郎です。

上のコードは、ichiroオブジェクトのgreet関数を、greeting()に引数(コールバック関数)として渡しています。greeting()関数内で実行してみると、一郎ではなく「私の名前は二郎です。」と表示されます。つまり、関数として実行した場合と同じ挙動になります。11行目のコールバック関数を実行する際、頭にオブジェクト名を付けていない点に注目して下さい。

thisの束縛(bind)

bindメソッドと使い方

JavaScriptのbindメソッドは、関数に対して新しいthisの値を指定するために使われるメソッドです。これをbindによるthisの束縛と表現します。bindメソッドを使用することで、関数内でのthisの値を明示的に指定し、新しい関数を生成することができます。これにより、関数を特定のオブジェクトにバインドして、そのオブジェクトのコンテキストで関数を実行することができます。

以下のように使用されます。

 関数.bind(コンテキスト, 引数1, 引数2, ...);

関数にはbindメソッドを呼び出す関数オブジェクトを指定します。コンテキストには関数内でthisとして参照されるオブジェクトを指定します。引数1, 引数2, …には関数に渡す引数を指定します。

以下が、bindメソッドの使用例です。

 function greet(greeting) {
   console.log(greeting + this.name);
 }
 
 const obj = {
   name: "裕二"
 };
 
 const bindGreet = greet.bind(obj, "こんにちは");
 boundGreet();
 // 出力: "こんにちは裕二"

bindメソッドによってbindGreet関数が新たに生成され、thisの参照先が引数のobjに束縛されます。結果、this.nameはobjのnameプロパティを指していることになり「こんにちは裕二」が表示されます。

bindメソッドの主のな利用用途は、コールバック関数として渡す関数のthisや引数を束縛する場合です。例えば、オブジェクトのメソッドをコールバック関数として渡した場合、関数として実行されるためthisの参照先はwindowオブジェクトになってしまいます(「コールバック関数でのthisの挙動」を参照)。しかし、bindメソッドで束縛することで任意の参照先に変更できます。

 window.name = "薔薇";
 
 const obj = {
     name: "向日葵",
     flower: function() {
         console.log("この花は" + this.name);
     }
 }
 
 setTimeout(obj.flower, 1000);
 setTimeout(obj.flower.bind(obj), 2000);

上記のコードは、setTimeout関数を使い第一引数に指定した関数を、第二引数で指定したミリセコンド秒後に実行させています。10行目は束縛していないためthisはWindowオブジェクトを参照し”薔薇”と表示されます。11行目はbindメソッドでthisの参照先をobjに束縛しているため”向日葵”と表示されます。

callメソッド

JavaScriptのcallメソッドは、bindメソッドと同じくthisの束縛に使われるメソッドです。bindメソッドと異なるのは、bindメソッドが新しい関数を生成するだけなのに対し、callメソッドは新しい関数を生成したら、即時に実行する点です。

以下のように使用されます。

 関数.call(コンテキスト, 引数1, 引数2, ...);

関数にはcallメソッドを呼び出す関数オブジェクトを指定します。コンテキストには関数内でthisとして参照されるオブジェクトを指定します。引数1, 引数2, … には関数に渡す引数を指定します。即ち、bindメソッドと同じ形式です。

callメソッドの使い方は以下です。

 const obj = { name: "裕二" };
 
 function greet(greeting) {
     console.log(`${greeting}、${this.name}`);
 }
 
 greet.call(obj, "初めまして");
 // 出力: 初めまして、裕二

callメソッドを実行した時点で、greet関数が即時に実行されています。

applyメソッド

JavaScriptのapplyメソッドは、bindメソッド、callメソッドと同じくthisの束縛に使われるメソッドです。applyメソッドはcallメソッドと同じく新しい関数を生成し、即時に実行します。callメソッドと異なるのは引数の束縛に配列を使う点です。

以下のように使用されます。

 関数.apply(コンテキスト, [引数1, 引数2, ...]);

関数にはapplyメソッドを呼び出す関数オブジェクトを指定します。コンテキストには関数内でthisとして参照されるオブジェクトを指定します。[引数1, 引数2, …]には関数に渡す引数を配列として指定します。

applyメソッドの使い方は以下です。

 const obj = { name: "裕二" };
 
 function greet(greeting, name) {
     console.log(`${greeting}、${name}`);
 }
 
 greet.apply(null, ["初めまして", "裕二"]);
 // 出力: 初めまして、裕二

greet関数の引数greetingとnameを、配列の”初めまして”と”裕二”で束縛しています。greet関数内ではthisを使わないため、applyメソッドの第一引数はnullを指定しています。

コメント

タイトルとURLをコピーしました