noteのタイトル画像

this is a 「???」 javascriptと「this」の意外な関係

目次
・this is a ???
・this とは自分自身
・javascriptはオブジェクト指向言語ではない?では何?
・thisは使うところを間違えると、ハマる
・メソッド呼び出しと関数呼び出し
 ・進化するが故のややこしさ
・ラムダ式参戦
・進化は続くよどこまでも

●this is a ???

私が子供の頃の土曜日20時は「8時だよ全員集合」という国民的娯楽番組を家族全員でお茶の間で見ていたものです。

「this is a pen」

上記のフレーズを「ほとんど意味のないタイミング」で発する「不条理ギャグ」が流行り。私は上記のフレーズの意味すら分からないのに馬鹿受けしていました。

「this(ディス)」って何? penはあの「ペン」???

思い返せば、this とは非常に長いお付き合いになります。
(未だに英語は不得意です。最近はどちらかというと中国語の方が親近感があります)

今回はJavaScriptでときどき登場して、意外と面倒な「this」の効能についてお話しましょう。

●this とは自分自身

私がアセンブラやBASICを卒業し、C言語やPascalを勉強していた頃は「構造化プログラミング」全盛の時代でした。

その後「オブジェクト指向っていうのが凄いらしい」ということで時代のトレンドに乗ろうとオブジェクト指向の勉強を始めた頃にこの「this」に出会うのです。

オブジェクト指向言語のC++やC#,Javaなどでclassを作成したとき、クラスの内部のどこででも利用できる参照に「this」があります。

C++で書くとこんな感じでしょうか?
(C++をメインで使わなくなって10年以上も経つことに驚きます)

class A {
    char *c;

    A() {}

    void func(char *x) {
        this->c = x;
    }
}

「this」はクラス(オブジェクト)自身を指し示すポインタになります。

●javascriptはオブジェクト指向言語ではない?では何?

巷では色々な情報が溢れかえっていて、正しい意味にたどり着くのは結構面倒です。

おそらくは「JavaScriptは”プロトタイプベース”のオブジェクト指向言語」ってことになるでしょう。

じゃあ、C++とか他の言語は?

これらは「”クラスベース”のオブジェクト指向言語」に分類されるようです。

オブジェクトを作成する機構がクラスベースとプロトタイプベースでは異なってきます。

とはいえ、昨今ではJavaScriptの言語仕様に「class」が実装されて

const val = new class();

なんて記述を見ると

「あれ?これってクラスを生成しているから、クラスベースなんじゃね?」

って思うこともまた事実。

で、「じゃあ、classをバンバン作って、オブジェクト指向しちゃおう🎵」

って安易に考えて「this」を多用すると、ハマります。

●thisは使うところを間違えると、ハマる

C++などの言語を使ってからjavascriptなどのプロトタイプベース言語を使うと、まずこのthisの扱いが厄介です。

以下のプログラムはどの「this」を指しているのでしょう。

console.log(this);

上記のプログラムを実行すると、エラーになることもなく

{}

と値が表示されます。空のオブジェクトのようです。

すると「this」っていったいどこを指している?ってことになりますよね。

どうやら「実行空間全体(つまりグローバル)に存在する何かを指し示して」いるようです。

なので、

this.value = 'hoge';

console.log(this);

を実行すると、次のように出力されます。

{ value: 'hoge' }

これではあまりに安直なので、もう少し実際に即した書き方をしてみましょう。

昔々、クラス構文がなかったころのjavascriptはプログラム全編が「function」の塊でした。

どこをみても「function」・・・。C/C++を経てC#/Javaなどのプログラミング言語を経験した私は、この「全編functionの羅列」を見て

なんて前近代的な言語だ

って思ったものです。

さて、thisがプログラム全体を覆っている「何か」であることはわかったので、次のように書いてみましょう。

this.value = 'hoge';    // thisの変数valueに設定
var g_value = 'bar';    // グローバル変数のg_value

function a() {
    return this.value;
}

function b() {
    return g_value;
}

// 出力
console.log(this);
console.log(a());
console.log(b());

結果は

{ value: 'hoge' }
undefined
bar

となりました。

あれ?function a()で出力した「this.value」の値が「undefined」になってしまっています。

単に「this」を出力した場合はちゃんと値を持って来れるのに、functionの内部では「this」参照できていません。

グローバル変数のg_valueは、そのまま出力できています。

どうやら「this」を媒介した値渡しがうまくいっていないようです。

この辺りから「this」の不可解さが漂ってきますね。

●メソッド呼び出しと関数呼び出し

実際にプログラムを作成していく段階で、上記のようにベタ書きすることはまず無いでしょう。
もう少し「現実的な」プログラムで「this」の差を見ていきましょう。

クラスにしろオブジェクトにしろ、何かを操作するのは
・メソッド
・関数(ファンクション)
で行います。

メソッドと関数、、、これって何が違うのでしょうか?

要は「オブジェクトのメソッドとして呼び出されるか、function(関数)で呼び出されるか」の違いです。

先ほど「javascriptはfunctionの塊」だと言いました。

以下はファンクション(関数)での例です。

function func() {
    console.log(this);
}
func();

を実行してみましょう。実行結果は

{ console: [Getter],
  DTRACE_NET_SERVER_CONNECTION: [Function],
  DTRACE_NET_STREAM_END: [Function],
  DTRACE_HTTP_SERVER_REQUEST: [Function],
  DTRACE_HTTP_SERVER_RESPONSE: [Function],
  DTRACE_HTTP_CLIENT_REQUEST: [Function],
  DTRACE_HTTP_CLIENT_RESPONSE: [Function],
  global: [Circular],
  process:
   process {
     title: 'node',
     version: 'v9.9.0',
     versions:
      { http_parser: '2.7.0',

 ・・・(中略)・・・

のように「どえらい数」の情報が出力されました。

一体どうしたことでしょうか!

functionの中に包んだだけで、先ほどは見えなかったグローバル(であろう)な情報が一気に出力されたのです。

次にメソッドにしてみましょう

var a = {
    func: function() {
        return this;
    }
}
console.log(a.func());

実行結果は

{ func: [Function: func] }

とだけ出力されました。

どうやら、オブジェクト「a」の内容を出力しているようです。
つまり「(おそらく)期待した」動作になっています。

では、メソッド内部でファンクションを定義したら?

やってみましょう。

var a = {
    func: function() {
        function b() {
            return this;
        }
        console.log(b());
    }
}
a.func();

実行結果は

{ console: [Getter],
  DTRACE_NET_SERVER_CONNECTION: [Function],
  DTRACE_NET_STREAM_END: [Function],
  DTRACE_HTTP_SERVER_REQUEST: [Function],
  DTRACE_HTTP_SERVER_RESPONSE: [Function],
  DTRACE_HTTP_CLIENT_REQUEST: [Function],
  DTRACE_HTTP_CLIENT_RESPONSE: [Function],
  global: [Circular],
  process:
   process {
     title: 'node',
     version: 'v9.9.0',
     versions:
      { http_parser: '2.7.0',
        node: '9.9.0',
        v8: '6.2.414.46-node.22',
 ・・・(中略)・・・

あらあら、またグローバルの「this」の情報が出力されてしまいました。

メソッドはオブジェクト内部のthisを参照していたのに、関数はグローバルの「this」を参照してしまったのです。

●進化するが故のややこしさ

昨今のjavascriptはクラス構文が導入されたりして、一気にクラスベースのオブジェクト指向言語の様相を呈してきました。

しかし、もともとはプロトタイプベースのオブジェクト指向言語なので、旧来のやり方で書かないとうまくいかない部分も多分にあります。

上記の「this」を「正しく」認識させるには、

var a = {
    func: function() {
        var me = this;
        function b() {
            console.log(me);
            return me;
        }
        console.log(b());
    }
}
a.func();

のように、一旦は「変数」で受けて、その変数を参照させる方法で対応できます。結果は

{ func: [Function: func] }
{ func: [Function: func] }

のようになり、おそらく「ちょっと変だけど期待した」結果でしょう。

●ラムダ式参戦

javascriptは進化しつづけています。
その分、頼もしくもあり、強力な言語に進化していく過程を見ているのは楽しいものです。

関数の書式にラムダ式が登場しました。

const func = () => {
    console.log(this);
}
func();

ラムダ式が導入されて、関数が
 const 関数名 = () => { ・・・}
のように記述できるようになりました。
functionと書くよりも完結に記述できるのが特徴です。。。が。

上記を実行すると

{}

またまた「あれ?」です。

先ほどの

function func() {
    console.log(this);
}
func();

と明らかに「this」の取っているスコープが異なりますね。

●進化は続くよどこまでも

このように、たかが「this」されど「this」です。

javascriptはいくつもの拡張を経て、さらに進化しようとしています。
この強力な言語はブラウザ標準言語の枠を超えて、いま、サーバサイドシステム記述の主要言語の仲間入りを果たしました。

これからもjavascriptの進化から目が離せないですね。

ご期待いただければ「スキ」ボタンをポチっと、「フォロー」ボタンをクリック、よろしくお願いいたします。

note: https://note.mu/o_matsuo

twitter: @o_matsuo
もフォローしてくださると、喜びます。

あ、それから私の師匠である
コンドウ様のnoteもポチっとしていただけると、さらに喜びます。

みなさんと一緒に、楽しいBotライフを🎵






ソフトウェア・エンジニアを40年以上やってます。 「Botを作りたいけど敷居が高い」と思われている方にも「わかる」「できる」を感じてもらえるように頑張ります。 よろしくお願い致します。