質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

6回答

4115閲覧

apply()やcall()を使う機会を知りたい

souta-haruran

総合スコア88

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

2グッド

4クリップ

投稿2017/01/31 04:04

###apply()、call()メソッドについて
この2つのメソッドについては、使用している実際のコードを目にすることもあり、これまでにもネットで調べて自分でカスタマイズしつつ検証してみたことはあり、何となく掴めかけてる気はするのですが、実際の実務では使いどころがイメージできておらず使用したことがありません。

どんな時にこの2つのメソッドを使うと便利なのでしょうか?
便利でなくとも、使う意味がある。というシーンはどんな時なのかご教示頂ければと思います。

###該当のソースコード

javascript

1 2// 昔調べたときに目にしたサイトのコードを一部引用 3// 昔なので引用元url見つかりませんでした。。 4 5// apply()を使った例 6function Teki() { 7 this.hp = 100; 8} 9 10function Dragon() { 11 Teki.apply(this,arguments); 12} 13Dragon.prototype = new Teki; 14 15Dragon.prototype.attack = function() { 16 console.log("1...ドラゴンの攻撃! ドラゴンのHPは"+this.hp+"だ!"); 17}; 18 19var boss = new Dragon(); 20boss.attack(); // 1...ドラゴンの攻撃! ドラゴンのHPは100だ! 21 22// call()を使った例 23function Teki2() { 24 this.hp = 200; 25} 26 27function Dragon2() { 28 Teki2.call(this); 29} 30Dragon2.prototype = new Teki2; 31 32Dragon2.prototype.attack = function() { 33 console.log("2...ドラゴンの攻撃! ドラゴンのHPは"+this.hp+"だ!"); 34}; 35 36var boss2 = new Dragon2(); 37boss2.attack(); // 2...ドラゴンの攻撃! ドラゴンのHPは200だ!

上記どちらもそれぞれapply()、call()を使用していますが、例えばcall()を下記みたいに書き換えてみます。

javascript

1function Teki2() { 2 this.hp = 200; 3} 4 5function Dragon2() { 6 Teki2(); 7} 8Dragon2.prototype = new Teki2; 9 10Dragon2.prototype.attack = function() { 11 console.log("2...ドラゴンの攻撃! ドラゴンのHPは"+this.hp+"だ!"); 12}; 13 14var boss2 = new Dragon2(); 15boss2.attack(); // 2...ドラゴンの攻撃! ドラゴンのHPは200だ!

このように同じ結果が得られています。
こういう感じの理解しかできておらず、使いどころがイメージ出来ていません。

すみませんが、ご教示よろしくお願いします。

farthest1999, yohhoy👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

prototype 上の関数を静的関数として使う

Array.prototype 上の関数を疑似配列に適用するのは比較的よく使われますが、この手法は他の関数にも応用できます。

JavaScript

1Array.prototype.map.call(document.querySelector('p'), function (element) { 2 return element.textContent; 3});

this 値が意図せぬ値になってしまう場合に正規の this 値に戻す

addEventListener, Array.prototype.forEach 等のコールバック関数で prototype 上の関数を使いたい場合、this 値が維持されない為に call, apply, bind を使う場合があります。

JavaScript

1// bad 2Hoge.prototype.something = function something () { 3 document.addEventListener('click', function handleClick (event) { 4 // this === document なので this.foo(); で Hoge.prototype.foo を呼び出せない 5 this.foo(); 6 }, false); 7} 8 9// good 1 (Function.prototype.bind) 10Hoge.prototype.something = function something () { 11 document.addEventListener('click', function handleClick (event) { 12 // bind で this 束縛したので this.foo(); で Hoge.prototype.foo を呼び出せる 13 this.foo(); 14 }.bind(this), false); 15} 16 17// good 2 (アロー関数) 18Hoge.prototype.something = function something () { 19 document.addEventListener('click', (event) { 20 // アロー関数は上位スコープの this を引き継ぐので this.foo(); で Hoge.prototype.foo を呼び出せる 21 this.foo(); 22 }, false); 23} 24 25// good 3 (addEventListener の listener オブジェクト) 26Hoge.prototype.handleEvent = function handleEvent (event) { 27 this.foo(); 28} 29Hoge.prototype.something = function something () { 30 document.addEventListener('click', this, false); // addEventListener の機能によって第二引数のオブジェクトに this 束縛される 31}

可変長引数に apply()

可変長引数と Function.prototype.apply は相性が良く、静的関数であっても良く使います。

JavaScript

1Math.max.apply(null, [1, 2, 3, 4, 5]); // 5

ただし、this 値を変更せずに可変長引数を配列で渡したい場合は rest parameters しかないので、ES5 で実装する時には苦労します。

「該当のソースコード」について

質問文中にある「該当のソースコード」ですが、この場合は Function.prototype.apply を利用する必要がないと思います。

JavaScript

1function Battle () { 2 this.count = 0; 3} 4 5Battle.prototype.enemyAttack = function enemyAttack (enemy) { 6 console.log(++this.count + ': ' + enemy.name + 'の攻撃!' + enemy.name + 'のHPは' + enemy.hp + 'だ!'); 7} 8 9function Enemy (name, hp) { 10 this.name = name; 11 this.hp = hp; 12} 13 14var battle = new Battle, 15 slime = new Enemy('スライム', 8), 16 dragon1 = new Enemy('ドラゴン1', 100), 17 dragon2 = new Enemy('ドラゴン2', 200); 18 19battle.enemyAttack(slime); // 1: スライムの攻撃!スライムのHPは8だ! 20battle.enemyAttack(dragon1); // 2: ドラゴン1の攻撃!ドラゴン1のHPは100だ! 21battle.enemyAttack(dragon2); // 3: ドラゴン2の攻撃!ドラゴン2のHPは200だ!

Re: souta-haruran さん

投稿2017/01/31 04:40

編集2017/01/31 06:55
think49

総合スコア18162

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

souta-haruran

2017/01/31 13:55

think49さん ありがとうございます。 何例も回答をご提示いただきありがとうございます。 prototype、thisなど具体的な回答も示していただいて勉強になりました。
guest

0

そういうケースよりは、例えばjQueryオブジェクトに純粋なJSのmap関数を使ったりとか、
Math.max関数の引数に配列を適用したりだとか、
配列用メソッドを持たない配列のようなオブジェクトに対して、配列用のメソッドを使用するケースが考えられます。

jQueryオブジェクトにJavaScriptのmapメソッドを使用する

$el.map()という風に記述すると、jQueryで定義されたmapメソッドを実行してしまいます。

javascript

1var $el = $('.sample'); 2 3var result = [].map.call($el, function(item, index){ 4 // 処理 5 var $item = $(item); 6});

配列の最小値・最大値を求める

Math.max Math.minの引数に指定できるのは通常だと数値ですが、applyを併用すると配列ごと渡せます。

javascript

1Math.max.apply(null, array); 2Math.min.apply(null, array);

配列のようなオブジェクトに対して、配列用のメソッドを使用する

例えば、document.querySelectorAll()で取得できるオブジェクトはNodeListと呼ばれる配列のようなオブジェクトです。
NodeListは配列用のメソッドをそのままでは使用できませんが、callやapplyを使用することで配列用メソッドを使えるようになります。

javascrpt

1var el = document.querySelectorAll('.class'); 2 3el.slice() // 不可 4el.map() // 不可

call+sliceを使って配列に変換するパターン

javascript

1var el = document.querySelectorAll('.class'); 2 3// 変換1 4var list = [].slice.call(el); 5 6// 変換2 7var list = Array.prototype.slice.call(el) 8 9list.map() // 使える

applyを使って変換するパターン

javascript

1var el = document.querySelectorAll('.class'); 2 3// 変換3 4var list = Array.apply(null, el); 5 6list.map() // 使える

投稿2017/01/31 04:22

編集2017/01/31 10:45
yamato_hikawa

総合スコア2092

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

souta-haruran

2017/01/31 14:07

yamato_hikawaさん ありがとうございます。 具体的な使用シーンを頂きました。 こういう場面でこういう使い方をすることでこういう結果が得られる。 とイメージできました。
guest

0

下記は現代的JS原理主義者による文章です。彼はIE排他主義者でもあります。それを理解の上、お読みください。


もはやapply()call()は必要とされていません。もし、使うことがあるとすれば、パフォーマンス改善のためのリファクタリングを行うときですが、その機会は極僅かです。

###質問のソースコードの手法が古い

まず、質問のソースコードは、プロトタイプベースで継承を行う為の古い手法です。この手法にはコンストラクタが余計に呼ばれるという根本的な問題があり、現代的なJavaScriptではもはや使われていません。現代的なJavaScriptではクラスベースで継承を行います。

JavaScript

1class Teki { 2 constructor() { 3 this.hp = 100; 4 } 5} 6 7class Dragon extends Teki { 8 attack() { 9 console.log("1...ドラゴンの攻撃! ドラゴンのHPは"+this.hp+"だ!"); 10 }; 11} 12 13const boss = new Dragon(); 14boss.attack(); // 1...ドラゴンの攻撃! ドラゴンのHPは100だ! 15 16class Teki2 { 17 constructor() { 18 this.hp = 200; 19 } 20} 21 22class Dragon2 extends Teki2 { 23 attack() { 24 console.log("2...ドラゴンの攻撃! ドラゴンのHPは"+this.hp+"だ!"); 25 } 26} 27 28const boss2 = new Dragon2(); 29boss2.attack(); // 2...ドラゴンの攻撃! ドラゴンのHPは200だ!

ここにapply()call()が入り込む余地はありません。

###複数の引数を配列としてまとめて渡すのにapply()は不要

apply()は複数の引数を配列として渡すことができます。しかし、...argを使えば、普通の呼び出しでも配列を分割して複数の引数にしてくれます。apply()を使う必要はありません。

JavaScript

1const f = (a, b, c) => a + b + c; 2const a = [2, 3, 5]; 3console.log(f.apply(this, a)); // × 4console.log(f(...a)); // ○

###ジェネリックメソッドは混乱の元になるため、使用すべきではない

他にapply()call()の使い方として、ジェネリックメソッド(generic method)があります。ArrayとStringのメソッドは意図的にジェネリック(intentionally generic)として定義されており、これがジェネリックメソッドです。しかし、ほとんどのメソッドにおいては、単純にArrayやStringに変換してからそのメソッドを適用しているに過ぎず、あえて使うメリットはありません。

JavaScript

1const list = { 2 0: 'a', 3 1: 'b', 4 2: 'c', 5 length: 3 6}; 7console.log(Array.prototype.join.call(list)); // × 8console.log(Array.from(list).join()); // ○ 9console.log(String.prototype.search.call(list, 'b')); // × 10console.log(String(list).search('b')); // ○

Array.prototype.push()など副作用を伴うメソッドはapply()call()を使うしかありませんが、意図通り動くかどうかはthisとして渡すオブジェクト次第になります。

JavaScript

1const list = { 2 0: 'a', 3 1: 'b', 4 2: 'c', 5 length: 3 6}; 7Array.prototype.push.call(list, 'd'); 8console.log(list);

これがどのような結果になるかをすぐに予測できなければ、使わない方がいいでしょう。もっとも、全てのオブジェクトはimmutableに扱うべきであり、副作用を伴うメソッド自体を使用すべきではありません、と関数型原理主義者が言っていました。


以上ですが、わりと本気で、このようにあるべきだと私個人は思っています。。

投稿2017/01/31 12:16

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

souta-haruran

2017/01/31 13:47 編集

raccyさん ありがとうございます。 現代的な書き方という点を含めいい勉強させてもらうきっかけになりました。 更には色々なパターンで例をいただき大変感謝しております。
guest

0

有用な使いみちとして、「thisを入れ替えて実行できる」ということがあります。

今はArray.fromというものがありますが、なかった頃にargumentsやノードリストなどの「配列のようなオブジェクト」を配列に変換するために、Array.prototype.slice.call(arguments)のように実行することがあります。

あと、.applyは引数リストを配列で渡せます(今は...配列で展開できますが)。

投稿2017/01/31 04:24

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

souta-haruran

2017/01/31 14:05

maisumakunさん ありがとうございます。 > thisを入れ替えて実行できる 分かりやすい言葉でした。 Array.fromというものについてもまだ勉強不足で知らなかったものなので、 頂いた回答をもとに勉強していきます。
guest

0

ベストアンサー

結論から言えば上の例(callを使った方)が良いですね。
上の例と下の例は応用した時に違いが生まれます。

JavaScript

1// 上の例 2var hoge; 3hoge = boss2.attack; 4hoge() // 2...ドラゴンの攻撃! ドラゴンのHPはundefinedだ! 5 6// 下の例 7var hoge; 8hoge = boss2.attack; 9hoge() // 2...ドラゴンの攻撃! ドラゴンのHPは200だ!

HPはundefinedって超絶強そうですね(小並感


callを使った上の例の場合、そのものにスコープが行きます。
これはbossのHPを変更した場合に顕著になります。

JavaScript

1var hoge; 2boss2.hp = 3000; 3boss2.attack(); // 2...ドラゴンの攻撃! ドラゴンのHPは3000だ! 4hoge = boss2.attack; 5hoge(); // 2...ドラゴンの攻撃! ドラゴンのHPはundefinedだ!

callを使わない下の例の場合、こうなります。

JavaScript

1var hoge; 2boss2.hp = 3000; 3boss2.attack(); // 2...ドラゴンの攻撃! ドラゴンのHPは3000だ! 4hoge = boss2.attack; 5hoge(); // 2...ドラゴンの攻撃! ドラゴンのHPは200だ!

これはTeki2の下位概念にDragon2インスタンスが作られ、boss2という名前が与えられますが、
boss2自体のプロパティにはhpは存在しません。
なのでスコープチェーンに従い、boss2の上位にあるTeki2.hpの値が利用されます。

しかし、今回boss2.hp = 3000と外部からboss2インスタンス自体のプロパティをいじったので、
boss2.hpは3000だけど、Teki2.hpは200というチグハグな状況が生まれます。
これを「種族HPだからMaxHPは親を辿って参照しよう!」という使い方も考えられますが、ちょっとイレギュラーなのでやめておいたほうがいいかと思います。


お次はapplyの解説です。
ぶっちゃけQiitaの記事がまとまり過ぎてて説明する余地がほぼ無いのですが…

関数型プログラミングとJavaScriptは相性が非常に良く、私は大抵のケースをリスト操作で片付けます。
リスト操作を覚えれば、殆どのケースでfor文を使う必要がなくなります。

applyは配列から関数を叩ける、thisを誘導出来るという2点が魅力です。
その結果、配列を持ってて加工したいケースが多いのでapplyは重宝します。

例として生徒の国語の最高得点、最低得点、平均点をJavaScriptで求めてみましょう。
今回は前者の「配列から関数を叩ける」点がメリットとして働いている事が分かるかと思います。
多少イディオムを使っているとはいえ、たったこれだけの行数で済むのは力強いですね。

JavaScript

1var user1 = { 2 kokugo: 80, 3 sugaku: 75 4}; 5var user2 = { 6 kokugo: 85, 7 sugaku: 60 8}; 9var user3 = { 10 kokugo: 40, 11 sugaku: 90 12}; 13var users = [user1, user2, user3]; // こういうケースはNode.jsやSPAでJSのカバー範囲が広くなると頻出します 14var output_nums = function (nums) { 15 var mean = function () { 16 var plus = function (a, b) {return a + b;}; 17 var sum = Array.prototype.slice.call(arguments).reduce(plus); 18 return Math.round(sum / arguments.length); 19 } 20 console.log(Math.max.apply(null, nums)); 21 console.log(Math.min.apply(null, nums)); 22 console.log(mean.apply(null, nums)); 23} 24 25output_nums(users.map(function(it){return it.kokugo})); 26// 85 27// 40 28// 68

投稿2017/01/31 06:00

miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

souta-haruran

2017/02/02 01:04 編集

miyabi-sunさん ありがとうございます。 私の例を踏襲したわかりやすい形でのご回答に大変感謝しております。 call()、apply()と質問に対する回答を細かくいただけて勉強になります。 今回は質問の例に沿ってご回答いただいたこちらをベストアンサーに選ばせていただきました。
guest

0

たとえば、

js

1function Dragon2() { 2 Teki2(); 3}

のところでTeki2()を実行していますが、
Teki2のコードは

js

1function Teki2() { 2 this.hp = 200; 3}

となっていますが、この時のthisはwindowになります。
ですのでこのコードを実行してみると、windowにhpというプロパティが生成されており値が200になっていると思います。
こういったときに

js

1function Dragon2() { 2 Teki2.call(this); 3}

とすることで、呼ぶ関数内でのthisをapply()やcall()で渡すことで変更することができます。

投稿2017/01/31 04:37

turbgraphics200

総合スコア4267

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

souta-haruran

2017/01/31 14:03

turbgraphics200さん ありがとうございます。 端的に示していただきわかりやすい回答でした。 > この時のthisはwindowになります。 言われたらその通りですよね。 全般通して分かりやすかったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問