JavaScript中級者へのステップアップ!callメソッドの使い所がわからないという方へ

こんにちは。TECH DRIVEの小笠原です。
今回は、JavaScriptのcallメソッドについてご紹介をしたいと思います。
callメソッドの使い所がいまいちわからないという方を対象としております。
また、本記事はJavaScriptにおける以下の知識があることが前提となりますので、予めご了承くださいませ。

  • thisの性質を理解している
  • プリミティブ値以外がオブジェクトであることを理解している
  • prototypeを理解している

callメソッドを知ろう

callメソッドは「関数を実行するための関数」です ※ 。
※ 同様のことを実現するメソッドとしてapplyが存在しますが、引数の渡し方等が異なるものの、実現できることはほぼ同じのため、applyの解説は割愛します。

「関数を実行するための関数」と聞いて「?」が浮かんだ方も多いのではないでしょうか?
順を追ってご説明していきます。

まずは、callメソッドの使い方をみてみましょう。

関数.call(thisに指定する値, 呼び出した関数に渡す引数情報)

callメソッドは、レシーバ(呼び出し元となる関数)を実行するメソッドなのですが、その際、第一引数に「実行する関数内でのthis」を指定することができます。
また第二引数は、callメソッドで呼び出す関数に渡す引数となりますので、関数が引数を必要としない場合は省略可能です。

まずは以下のコードを見てください。

var person = {
  name: 'Ken',
  greet: function () {
    console.log('My name is ' + this.name) 
  }
}

var doc = {
  name: 'Pochi'
}

person.greet() // 結果: My name is Ken
doc.greet() // 結果: Uncaught TypeError: doc.greet is not a function

person/docという変数にオブジェクトを代入し、それぞれgreetメソッドを実行しています。 しかし、変数docに代入されたオブジェクトはgreetというメソッドを持たないため、エラーとなっていますね。 これはみなさんも期待どおりの結果なのではないでしょうか?
次にcallメソッドでgreetメソッドのthisをdocに指定し、実行してみたいと思います。

person.greet.call(doc) // 結果: My name is Pochi

なんと、Pochiがしゃべりました。
personが持つgreetメソッドのthisをdocに指定したことにより、まるでdocがgreetメソッドを持っているかのような振る舞いになりました。
誤解を恐れずに言うなら、callメソッドを使用することにより、あるオブジェクトのメソッドを拝借し実行することができるのです。

しかし、これだけではまだメリットが見えてきませんね。 次に以下のコードをみてください。

HTML

<li class="item">hoge</li>
<li class="item">fuga</li>
<li class="item">piyo</li>

JavaScript

var $els = document.getElementsByClassName('item') // 結果: HTMLCollection(3) [li.item, li.item, li.item]
for(var i = 0; i < $els.length; i++) {
  console.log($els[i].innerText)
} // 結果: hoge fuge piyo

これは、itemという名前のclassをもったli要素のテキスト情報を出力するための処理です。
ここで注目していただきたいのは、getElementsByClassNameメソッドの返り値である「HTMLCollection」です。
このHTMLCollectionは、インデックス番号からデータの参照が行えることからも、配列に非常によく似たオブジェクトと言えます。
そのため、上のコードを以下のようにforEachを使用し、よりスマートに処理を書けるように変更してみたいと思います。

var $els = document.getElementsByClassName('item')
$els.forEach(function(val){
  console.log(val.innerText)
})  // 結果: Uncaught TypeError: $els.forEach is not a function

しかし、上記のコードを実行するとエラーが発生してしまいます。
なぜならforEachは配列(Arrayオブジェクト)のメソッドであり、HTMLCollectionはそのようなメソッドを持っていないためです。
このことから、HTMLCollectionは、フォーマットこそ配列と似ていますが、似ているだけで全く異なるオブジェクトであることがわかります。

しかし、この問題は、以下のようにcallメソッドを使用することで解決します。

var $els = document.getElementsByClassName('item')
Array.prototype.forEach.call($els, function($el){
  console.log($el.innerText)
})  // 結果: hoge fuge piyo

ここでcallメソッドの使い方をもう一度みてみましょう。

関数.call(thisに指定する値, 引数情報)

今回の場合、callメソッドのレシーバとなる関数は Array.prototype.forEach となります。
そして、そして第1引数の$els(HTMLCollection)がforEach実行時のthisとなります。
第2引数は実行する関数(forEach)へ渡す引数です。

この結果、forEachはまるでHTMLCollectionがもつメソッドかのように振る舞います。 今回の様に「あるオブジェクトのメソッドを拝借したい」「関数実行時にthisの値を強制したい」と思った時、callメソッドという選択肢を覚えておくと役立つシーンもあるはずです。

まとめ

いかがでしょうか?
callメソッドは、「thisの性質」や「オブジェクトへのより深い理解」が求められることからも、JavaScript中級者向けのメソッドであることは間違いないでしょう。
またメリットが掴みづらいという点も、理解のハードルを上げてしまう要因かと思います。
本記事を通して、少しでもcallメソッドの理解を深めていただけたのなら幸いです。

JavaScript Climbing

JavaScriptに特化したトレーニング「JavaScript Climbing」を行なっています。
this、クロージャ、Class等、中〜大規模のフロントエンドの開発にのぞむにあたり、必須となるスキル習得を現役のWEBエンジニアが徹底してサポートします。
2週間の無料体験期間がございますので、ご興味がある方は是非以下のリンクより詳細をご覧ください。

circlearound.co.jp

個別トレーニング

短期間でぐっと成長したい方は弊社主催の個別トレーニングがおすすめです。 トレーニング内容は、受講者の方の課題/要望をお伺いした上で、フルオーダメイドで作成させていただきます。 詳細は以下のリンクよりご確認ください。(応募者多数の場合には時間を別途ご用意する予定です)。

WEBプログラミング個別トレーニング

TECH DRIVEについて

TECH DRIVEは「技術者の成長を加速させる」をキーワードに都内で活動をしているコミュニティです。
TwitterやFacebookにて技術ネタやイベント情報の発信を行っていますので、ご興味があれば、いいねやフォローをお願いいたします。