Help us understand the problem. What is going on with this article?

JavaScript の this を理解する多分一番分かりやすい説明

More than 1 year has passed since last update.

JavaScript の this は、(他のプログラム言語から見ると) ちょっと面白い挙動に見えることがあります。

先日、この this の挙動について、会社の同僚が説明してくれたのですが、これまで聞いた説明の中で一番分かりやすいと感じたので、頑張って日本語で説明してみます。

分かりにくかったら、多分それは私の技量不足。

thisfunction の関係

function が基準スコープになるのがまず一点。

その function をどう呼ぶかで変わるのかがもう一点。

それを踏まえて……

thisfunction を呼んだ時の . の前についているオブジェクトを指している

と理解できるというのが、同僚の説明でした。

. が省略された場合はグローバルオブジェクトになります (non-strict モード時)。 strict モードでは undefined になります。(@ryo511 さん、ご指摘感謝)

js_this_and_func.png

サンプルコード

これだけだとナンノコッチャかもしれないので、具体例を……

function test() {
    console.log(this)
}

this の内容をコンソールに表示するだけの、なんてことない関数です。

これをブラウザ上で呼び出すと……

test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}

のようになります。

呼び出し時に . がないので、 this はグローバルオブジェクト、ブラウザでは Window オブジェクトになります (non-strict モード)。

この関数 test を特定のオブジェクトに結びつけます。

function test() {
    console.log(this)
}
var obj = {}
obj.test = test

その上で、 obj.test() を呼び出すと、 thisobj になります。

obj.test() // => {test: ƒ}

関数 test を呼び出す際、 . がついていて、 obj. となっているので、関数 test の中では this = obj となるという訳です。

逆に、あるオブジェクトに結びついているものをグローバルスコープで呼び出すこともできます。

var obj = { test: function() { console.log(this) } }
var test = obj.test
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}

test()obj の関数 test を呼び出していることになりますが、呼び出し時に . がついていないので、グローバルオブジェクトが this になります (non-strict モード)。

上のサンプルコードでは、分かりやすくしていますが、例えば、setTimeout などで obj.test をコールバックとして渡すような場合でも同様のことが起きています。

メソッドチェーン

複数の function を数珠つなぎで呼び出すメソッドチェーンは . の前が関数になりますが、この場合、その関数が返すオブジェクトを参照することになります。

関数で return を省略したり、 return 単独で呼ぶと undefined が返ってくるので、メソッドチェーンは利用できません。

var obj = {
  test: function() { return this },
  alert: function(msg) { console.log(msg) }
}
var test = obj.test
obj.test().alert("hello") // => hello とコンソールに表示
test().alert("hello") // => アラート表示

関数 alert の呼び出しに . があって、その前に関数 test があります。

関数 test の返り値は this なので、その test の呼び出し方によって参照されるオブジェクトによって関数 alert の結果が変わります。

callapply

callapply を使って関数を呼び出すと . に前につけるオブジェクトを指定することができます。

function test() {
    console.log(this)
}
var obj = { name: "obj" }
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
test.call(obj) // => {name: "obj"}

test.call(obj)obj.test() に等しくなります (ただし、 objtest というメソッドは追加されません)。

コンストラクタ

function に対して、 new 構文を利用して、オブジェクトを作成することができます。

これは大雑把に言ってしまえば、 call の変形構文と考えることができます。

例えば、以下のコード……

var obj = new function() {
    this.name = "obj"
    console.log(this) // => {name: "obj"}
}

この時、 function が呼ばれますが、 this はグローバルオブジェクトでも undefined でもありません。

new を使うと、新規にオブジェクトを作成して、それに対して、 call を使って function を呼び出して、関数内部で return this するような動作になります。

つまり……

var obj = function() {
    this.name = "obj"
    console.log(this) // => {name: "obj"}
    return this
}.call({})

と同じ動作と考えることができます。

これは無名関数の場合ですが、名前付き関数 (例えば function Test()) の場合、内部でもう少し追加処理が入ります。 ただし、 this の挙動に関しては、基本的に同様に考えることができると思います。

bind

bind はこの . ルールの挙動を変化させて、強制的にあるオブジェクトと結びつけます。

function test() {
    console.log(this)
}
var obj = { name: "obj" }
var check = test.bind(obj)
check() // => {name: "obj"}

関数 check の呼び出しには . がついていませんが、 bind されているので、呼び出し時に obj. が付く形となり、関数 test の中で this = obj になります。

関数の中の関数

. はあくまでも関数呼び出し時に参照されるものです。

var obj = {
    test: function() {
        console.log(this) // *1
        function test() {
            console.log(this) // *2
        }
        test()
    }
}
obj.test()

のようなコードがあった時、

*1 では obj がコンソールに表示され、 *2 ではグローバルオブジェクト (例えば、 Window) が表示されます (non-strict モード時)。

*2 の関数呼び出しに . がないからですね。

var obj = {
    test: function() {
        console.log(this) // *1
        function test() {
            console.log(this) // *2
        }
        test.call(this)
    }
}
obj.test()

call を使えば (apply でもいいです)、どっちも obj になります。

まとめ

thisfunction を呼んだ時の . の前についているオブジェクトを指している

  1. 関数呼び出しの . の前のオブジェクトが this になる
  2. . を省略するとグローバルオブジェクトを参照する (non-strict モード)
    • strict モードでは undefined になる
  3. call, apply を使うと . の前のオブジェクトを指定できる (コンストラクタはこの変形型)
  4. bind を使うと . の前のオブジェクトを強制できる

(補足)アロー関数について

当記事では、あえて ES2015 から導入されたアロー関数について触れていませんでした。

アロー関数での this の振る舞いは (他のプログラム言語から見て) 比較的自然で分かりやすい気がします。

最初に触れたとおり

  • function が基準スコープになる

のが JavaScript の大きな特徴です (var のスコープもこれに準じます)。

そのため、function を入れ子にした途端 this の指すものが変わってきてしまうのが一番の混乱の要因です。当記事では、そこに焦点を当てて説明しています。

アロー関数は入れ子にしても、アロー関数の中で参照されている this が指すものは変わりません。そのアロー関数がキャプチャされたときの this を保持します。

justincase
テクノロジーで保険を身近にする保険スタートアップです。
https://justincase.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした