どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう」をお届けしました。
今回は、「いくつでも引数をとれる関数を呼び出すにはapplyを使おう」 をお届けします。
テキスト第3章「関数の扱い」の項目21に対応しています。
今日のアジェンダ
- argumentオブジェクトと可変長引数
- applyメソッド
- applyメソッドとカスタムレシーバ
argumentオブジェクトと可変長引数
引数を受け取って、平均を返す関数は、このようにargumentオブジェクトを使って表現できました。
argumentsは配列風のオブジェクトです。
そして、このように、引数の個数がわからなくても対応できる書き方を、可変長引数、または可変アリティ関数と呼びます。
※アロー関数が使えないので注意が必要です。
function myFunction3_21_01() {
'use strict'
/**
* 平均を返す関数
*/
const avarage = function () { //アロー関数は使えません
console.log(arguments); //{ '0': 10, '1': 0 }
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum / arguments.length;
};
console.log(avarage(10, 0)); //5
}
便利そうに見えますが、世の中には「引数の数は必ず1つにしたほうがわかりやすいよ」という、固定アリティ関数があります。
たとえば引数を配列にしてしまう方法です。
これだと、引数の数は固定になりましたが、先ほどの関数では対応できません。
function myFunction3_21_02() {
'use strict'
/**
* 平均を返す関数
*/
const avarage = function () { //アロー関数は使えません
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum / arguments.length;
};
console.log(avarage([10, 0])); //NaN
}
applyメソッド
これを解決するために、引数で渡すさいに、配列の要素を1つ1つバラして渡そうというメソッドがapplyメソッドです。
第1引数に、カスタムレシーバを指定できますが、今回はレシーバを使わないのでnullでかまいません。
function myFunction3_21_03() {
'use strict'
/**
* 平均を返す関数
*/
const avarage = function () { //アロー関数は使えません
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum / arguments.length;
};
console.log(avarage.apply(this, [10, 0])); //5
console.log(avarage.apply(null, [10, 0])); //5
}
これは、配列を引数として受け取り、配列を展開する処理とは、ちょっと意味が違うことだけ意識してください。
もちろん、このような処理でも正しく動きます。
function myFunction3_21_04() {
'use strict'
/**
* 平均を返す関数
*/
const avarage = array => {
const infinityArray = array.flat(Infinity);
const sum = infinityArray.reduce((previousValue, currentValue) => previousValue + currentValue);
const avarage = sum / infinityArray.length;
return avarage;
};
console.log(avarage([10, 0])); //5
console.log(avarage([[10, 0]])); //5
}
applyメソッドをどうしても使いたいなら、このように書くかもしれませんね。
※スプレッド構文は次回お届けします。
function myFunction3_21_05() {
'use strict'
/**
* 平均を返す関数
*/
const avarage = function () { //アロー関数は使えません
const array = Array.from(arguments);
const sum = array.reduce((previousValue, currentValue) => previousValue + currentValue);
const avarage = sum / arguments.length;
return avarage;
};
console.log(avarage.apply(this, [10, 0])); //5
console.log(avarage.apply(null, [10, 0])); //5
}
applyメソッドとカスタムレシーバ
データを一時的に保管しておく領域をバッファと読んだりしますが、このようなコードを書いてみました。
appendメソッドは可変長引数になっています。
function myFunction3_21_06() {
'use strict'
const buffer = {
state: [],
append() {
for (let i = 0; i < arguments.length; i++) {
this.state.push(arguments[i]);
}
}
};
buffer.append('Tsuji', ' ', 'Kenzo', '!!!');
console.log(buffer.state); // ['Tsuji', ' ', 'Kenzo', '!!!' ]
}
しかし、配列を引数として渡すと、配列の中に配列が入ってしまい、うまく動作しないようです。
function myFunction3_21_07() {
'use strict'
const buffer = {
state: [],
append() {
for (let i = 0; i < arguments.length; i++) {
this.state.push(arguments[i]);
}
}
};
/**
* 文字列を含む配列を返す関数
*/
const getInputStrings = () => {
return ['Sep-2021', '20 Mon'];
};
buffer.append('Tsuji', ' ', 'Kenzo', '!!!');
//配列がそのままappendされてしまう
buffer.append(getInputStrings());
console.log(buffer.state); //[ 'Tsuji', ' ', 'Kenzo', '!!!', [ 'Sep-2021', '20 Mon' ] ]
}
このようなときに、配列の要素をバラで引数に渡す(厳密には違いますが)applyメソッドが有効です。
しかし、applyメソッドの第1引数で、正しいレシーバを指定しないと、ぜんぜん関係ないオブジェクトのstateプロパティを更新しかねません。
nullを指定した場合も動きません。
function myFunction3_21_08() {
'use strict'
const buffer = {
state: [],
append() {
for (let i = 0; i < arguments.length; i++) {
this.state.push(arguments[i]);
}
}
};
/**
* 文字列を含む配列を返す関数
*/
const getInputStrings = () => {
return ['Sep-2021', '20 Mon'];
};
const birthDay = {
state: ['つじけさんの誕生日は']
}
buffer.append('Tsuji', ' ', 'Kenzo', '!!!');
buffer.append.apply(birthDay, getInputStrings());
console.log(birthDay.state); // [ 'つじけさんの誕生日は', 'Sep-2021', '20 Mon' ]
buffer.append.apply(buffer, getInputStrings());
console.log(buffer.state); //[ 'Tsuji', ' ', 'Kenzo', '!!!', 'Sep-2021', '20 Mon' ]
// buffer.append.apply(null, getInputStrings()); //TypeError: Cannot read property 'state' of null
}
まとめ
以上で、「いくつでも引数をとれる関数を呼び出すにはapplyを使おう」をお届けしました。
メソッドを記述して、呼び出す側でオブジェクトを指定する。。。なんだか、ポリモーフィズムの匂いがします。
気のせいですかね。
今回は、ずっとスプレッド構文を我慢していました。
次回は、「可変長引数関数を作るには、argumentsを使う」 をお届けします。
このシリーズの目次
- [EffectiveJavaScript輪読会3]関数、メソッド、コンストラクタの、呼び出しの違いを理解する
- [EffectiveJavaScript輪読会3]高階関数を快適に使えるようにしよう
- [EffectiveJavaScript輪読会3]カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう
- [EffectiveJavaScript輪読会3]いくつでも引数をとれる関数を呼び出すにはapplyを使おう
- [EffectiveJavaScript輪読会3]可変長引数関数を作るには、argumentsを使う
- [EffectiveJavaScript輪読会3]argumentsオブジェクトを書き換えない
- [EffectiveJavaScript輪読会3]argumentsへのリファレンスは変数に保存する