9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaScriptAdvent Calendar 2020

Day 14

JavaScriptの口寄せ的な存在? call()メソッド

Last updated at Posted at 2020-12-13

はじめに

JavaScriptのメソッドcall()とapply()の使い方、何ができるのか?サンプル(簡単な使用例)、thisの参照先の考え方(windowとは?)についてまとめています。

call()は呼び出すメソッド

call()は**使いたい関数を、thisの参照先を変えて、呼び出すメソッド**です。

  • call()は単語が意味するように何かを**呼び出すメソッド**
  • 呼び出す相手は**使いたい関数 = function**
  • 普通の関数呼び出しとの違いは**関数のthisの参照先を変えられる**

何ができるのか?

ここまでの説明では結局、何が出来るのか?ピンとこないと思います。
call()で出来ることをロールプレイングゲームで例えると、「呪文を使えない戦士が、僧侶の能力を自らに取り込んで、回復呪文を使う」。自分が持っていない誰かのスキルであっても、まるで口寄せのように使うことができる。そんなイメージかと思います。

call()の使い方

call()を使うための予備知識として、知っておくことは大きく分けて以下の2つ。

1. thisと参照先
2. 引数の使い方

1. thisの参照先とは?

thisとは「記述場所によって参照先の変わる変数」で、参照先(= 代入されるオブジェクト)は、functionをどのオブジェクトのメソッドとして呼び出したかで決まります。

グローバルな空間に定義した関数内のthisの参照先
var fn = function() { // グローバルに関数 fn を定義
  console.log(this);
};

fn(); // -> thisの参照先は window (グローバル)

グローバルに定義した関数内のthisはどこを参照するのかconsole.log()で確認してみると、**ブラウザ上でのグローバルオブジェクトを示す「window」**を参照します。
これは「グローバルに定義した関数 = windowオブジェクトのメソッド」であることを意味します。

オブジェクト「obj」に定義した関数内のthisの参照先
var obj = { // グローバルにオブジェクト obj を定義
  fn: function() { // メソッド fn を定義
    console.log(this);
  }
};

obj.fn(); // -> 関数内のthisの参照先は obj (そのオブジェクト自身)

オブジェクト内に定義したメソッド(関数)のthisは、そのオブジェクト自身を参照します。
この例ではオブジェクト「obj」のメソッドとしてfnを定義しているので、fnを呼び出すときにthisが参照するオブジェクトは「obj」になります。

またobj.fn()のようにオブジェクトのメソッドを呼び出すとき、.演算子の左に指定するオブジェクトを「レシーバ」や「レシーバオブジェクト」と呼んだりします。(この場合はobjがレシーバオブジェクト)

2. 引数の使い方

構文の確認

call()の構文
// 呼び出す関数名.call(thisの参照先, 引数1, 引数2,...引数N)
   func.call([thisArg[, arg1, arg2, ...argN]])

構文を基本の記述として、呼び出す関数名.関数内のメソッド.call(thisの参照先, 引数1, 引数2,...引数N)と記述すれば、呼び出す関数のthisの参照先を変更し、その関数内のメソッドを使うことができます。

引数 指定内容
第1引数 thisの参照先を指定
第2引数 以降 呼び出す関数で使用する引数を指定(配列は指定できない)

call()を使った関数の呼び出しでは、第1引数の指定で「thisの参照先を変える」ことができます。例えば、オブジェクト内に定義した関数内のthisの参照先を「obj」 → 「window」に変える。と言ったようなことができます。

第1引数を指定

var obj = { // グローバルにオブジェクト obj を定義
  fn: function() { // メソッド「fn」を定義
    console.log(this);
  }
};

// thisの参照先を確認
obj.fn(); // -> thisの参照先は obj
obj.fn.call(window); // 第1引数に window を指定 -> thisの参照先は window に変わる

obj.fn.call({}); // 第1引数に {} を指定 -> thisの参照先は {} に変わる
obj.fn.call([]); // 第1引数に [] を指定 -> thisの参照先は [] に変わる

第1引数はthisの参照先を指定します。
nullundefindを指定した場合、thisの参照先は「window」になります。

第2引数以降を指定

var fn = function(x, y) { // グローバルに関数 fn を定義
  console.log(x);
  console.log(y);
  console.log(this);
};
 
// 第1引数は「thisの参照先」を指定
// 第2引数以降は「呼び出す関数の引数」を指定
fn.call(window, 100, 'abc'); 

// -- 出力結果 --
// 100
// abc
// window
  • 第1引数で呼び出す関数の「thisの参照先」を指定し、固定します。
  • 第2引数では、呼び出す関数で使用する引数を指定します。複数指定は可能で、呼び出す関数の引数に対応しますが配列は指定できません。
  • 第2引数に配列を使いたい場合は apply() を使いましょう。(後述します)

apply()の使い方

call()に似たメソッドにapply()があります。
call()の予備知識があれば、apply()も使えるので紹介しておきます。

構文の確認

call()の構文
// 呼び出す関数名.apply(thisの参照先, [要素1, 要素2,...要素N])
   func.apply(thisArg, [ argsArray])
引数 指定内容
第1引数 thisの参照先を指定
第2引数 呼び出す関数で使用する引数を配列で指定

call()との大きな違いは第2引数を配列で指定すること

サンプル

call()の使用例

取り込み

thisの参照先を変更できる性質を利用し、自分が持っていない他の関数のメソッドを取り込んで使う。冒頭の「呪文を使えない戦士が、僧侶の能力を自らに取り込んで、回復呪文を使う」をcall()で2パターン書いてみます。

パターン1:オブジェクト
// 呼び出される
var priest = {
  job: '僧侶',
  healingSpell: function(target, lv) {
    console.log(this.job + '' + target + 'に回復呪文LV' + lv + 'を唱えた!');
  }
};

// 呼び出す
var fighter = {
  job: '戦士'
};

priest.healingSpell('勇者', 1); // 僧侶は勇者に回復呪文LV1を唱えた!
priest.healingSpell.call(fighter, '魔法使い', 3); // 戦士は魔法使いに回復呪文LV3を唱えた!

予備知識の説明に沿えばこんな感じに書けますが、

パターン2:コンストラクタ
// 呼び出される
var Priest = function () {
  this.job = '僧侶';
  this.healingSpell = function(target, lv) {
    console.log(this.job + '' + target + 'に回復呪文LV' + lv + 'を唱えた!');
  };
};

// 呼び出す
var Fighter = function () {
  this.job = '戦士';
};

new Priest().healingSpell('勇者', 1); // 僧侶は勇者に回復呪文LV1を唱えた!
new Priest().healingSpell.call(new Fighter(), '魔法使い', 3); // 戦士は魔法使いに回復呪文LV3を唱えた!

と言ったように、コンストラクタを利用して関数定義した方が使い勝手は良さそうです。

継承もどき

「取り込み」で書いたコンストラクタを利用した方法では、継承のような書き方もできます。

call()を利用した継承に似た方法
// 呼び出される
var Priest = function (job) {
  this.job = job;
  this.healingSpell = function(target, lv) {
    console.log(this.job + '' + target + 'に回復呪文LV' + lv + 'を唱えた!');
  };
};

// call()で他のオブジェクトを取り込む
var Fighter = function (job) {
  Priest.call(this, job); // 第1引数は this を指定する
};

// 呼び出す
var fighter = new Fighter('戦士');
fighter.healingSpell('魔法使い', 3); // 戦士は魔法使いに回復呪文LV3を唱えた!

// instanceof演算子で比較
console.log(fighter instanceof Priest); // false

instanceof演算子で比較すると、
prototypeで他のオブジェクトを取り込んだ場合はtrueとなり、同じコンストラクタから作成されたものということがわかります。
call()によるオブジェクトの取り込みはfalseとなり、prototypeによる継承とは異なります。

以下でprototypeを利用した書き方に変えてみます。

prototypeを利用した継承
// 呼び出される
var Priest = function (job) {
  this.job = job;
  this.healingSpell = function(target, lv) {
    console.log(this.job + '' + target + 'に回復呪文LV' + lv + 'を唱えた!');
  };
};

// prototypeで他のオブジェクトを取り込む
var Fighter = function (job) {
  this.job = job;
};
Fighter.prototype = new Priest();

// 呼び出す
var fighter = new Fighter('戦士');
fighter.healingSpell('魔法使い', 3); // 戦士は魔法使いに回復呪文LV3を唱えた!

// instanceof演算子で比較
console.log(fighter instanceof Priest); // true

prototypeで他のオブジェクトを取り込み継承した場合は、instanceof演算子の比較がtrueとなり、同じコンストラクタから作成されたものということがわかります。

apply()の使用例


var arr = [30, 50, 150, 10, 200];
var min = Math.min.apply([], arr); // 最大値を抽出
var max = Math.max.apply([], arr); // 最小値を抽出

console.log(min); // 10
console.log(max); // 200

使用機会はcall()の方がは多そうで、apply()は少なそうですが、「配列の要素から最大値や最小値を抽出」と言った場面ではapply()が活躍します。

まとめ

  • call()は使いたい関数を、thisの参照先を変えて、呼び出すメソッド。
  • thisの参照先は、関数がどのオブジェクトのメソッドとして呼び出されるか?によって変わる。
  • 第1引数に「thisの参照先」、第2引数にcall()で使いたい関数の引数に対応する。
  • 第2引数に配列を扱いたいときは、apply()を使う。

おわりに

何ができるメソッド?と聞かれると、説明がややこしい中級メソッドのcall()とapply()。
持ち主の関数オブジェクトのthisを乗っ取り、持ち主の力を使う。一言で表すなら「取り込み」。callの単語の意味には「呼ぶ」以外にも「寄せる」といった意味もあることから、少し大袈裟な連想で「口寄せ(?)」的な言葉が自分の中ではイメージしやすく、ピタッときます。「継承」はprototypeとかありますし。。その辺りの葛藤がタイトルや見出しなどに出ているので、タイトルはそのうち変更してしまうかもしれません。。
(使用例はもう1つくらい追加するかもしれません)

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?