###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だ!
このように同じ結果が得られています。
こういう感じの理解しかできておらず、使いどころがイメージ出来ていません。
すみませんが、ご教示よろしくお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答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総合スコア18162
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総合スコア2092
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
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
総合スコア21735
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/01/31 13:47 編集
0
有用な使いみちとして、「this
を入れ替えて実行できる」ということがあります。
今はArray.from
というものがありますが、なかった頃にarguments
やノードリストなどの「配列のようなオブジェクト」を配列に変換するために、Array.prototype.slice.call(arguments)
のように実行することがあります。
あと、.apply
は引数リストを配列で渡せます(今は...配列
で展開できますが)。
投稿2017/01/31 04:24
総合スコア145183
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/01/31 14:05
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
総合スコア21158
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/02/02 01:04 編集
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
総合スコア4267
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/01/31 14:03
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/01/31 13:55