どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「argumentsオブジェクトを書き換えない」をお届けしました。
今回は、「argumentsへのリファレンスは変数に保存する」 をお届けします。
テキスト第3章「関数の扱い」の項目24に対応しています。
今日のアジェンダ
- argumentsオブジェクトが参照するもの
- Symbol.iterator
argumentsオブジェクトが参照するもの
テキストでは、引数を順番に返す自作関数が書かれていました。
まずは、正しく動かないコードです。
これは、nextメソッド内に書かれている変数argumentsが、nextメソッド自身のargumentsオブジェクトを参照しているからです。
function myFunction3_24_01() {
/**
* 引数を順番に返す関数
*/
const values = function () {
let i = 0, n = arguments.length;
const obj = {
hasNext: function () {
return i < n;
},
next: function () {
if (i >= n) {
throw new Error('イテレーションの終わりです');
}
return arguments[i++];
}
};
return obj;
}
const it = values(1, 4, 1, 4);
console.log(it.next()); //undefined
console.log(it.next()); //undefined
}
解決策として、values関数のargumentsオブジェクトを変数aに格納し、nextメソッド内でaを明示的に参照しようという流れです。
function myFunction3_24_02() {
/**
* 引数を順番に返す関数
*/
const values = function () {
let i = 0, n = arguments.length, a = arguments;
const obj = {
hasNext: function () {
return i < n;
},
next: function () {
if (i >= n) {
throw new Error('イテレーションの終わりです');
}
return a[i++];
}
};
return obj;
}
const it = values(1, 4, 1);
console.log(it.next()); //1
console.log(it.next()); //4
console.log(it.next()); //1
console.log(it.next()); //Error: イテレーションの終わりです
}
答えだけみるとかんたんそうですが、いざバグと立ち向かったら、自分で気付く自身はありませんね。。。
Symbol.iterator
以上で、この項目は終わりなのですが、少し余白あるので、イテレータの考察をしてみましょう。
先ほどは、イテレータを自作しました。しかし、JavaScriptには、すでにイテレータが実装されています。
こちらのブログでシンボルについてかんたんに勉強してみました。
シンボルとは値であり、オブジェクトのプロパティ名として使用できるというものでした。
この仕様をつかって、ES6からSymbol.iteratorというメソッドが実装されました。
Symbol.iterator、実は目に見えるんです(2021年9月現在)。
ランタイムはV8のままで、スクリプトエディタを旧エディタに戻して、このようなコードを書いて、デバッグを見てみましょう。
function myFunction3_24_03(){
const number = 100;
const array = [1,2,3];
const string = 'Tom';
}
プリミティブ型のnameは、値をもつのみですね。タネも仕掛けもありません。
オブジェクトarray(配列もオブジェクトです)の中身は、lenghtプロパティを含む値と、**proto**という謎なものが格納されているようです。
__proto__をクリックすると、なにやらダーッと表示されます。
うわーっと両手で目をふさいでしまいますが、恐る恐る中身を見てみると、メンバー(プロパティとメソッド)が表示されています。
どうやら**メソッド(Functionオブジェクト)**が大量に格納されているようですね。
そのままスクロールすると、**Symbol(Symbol.iterator)**というFunctionオブジェクトがありますね。
これこそ、オブジェクト[array]に格納されている、Symbol.iteratorメソッドです。
詳細は割愛しますが、このようなお話です。
- Googleさんが、iteratorメソッドを自作してくれた。
- 引数を受け取ると、要素を順番に処理してくれる。
- ES6でこのメソッドを追加したいが、すでにiterator()というオリジナルメソッドを実装しているユーザーに迷惑をかけてしまう。
- Symbol型を用意したよ。Symbol値は唯一無二。
- Symbolはプロパティ名にも使えるよ。
- Symbol.iteratorを実装したらか、みんな使ってね。
Symbol.iteratorを拝借してみよう
この、Symbol.iteratorメソッドを、プリミティブ型に実装してみましょう。
function myFunction3_24_04(){
const name = 'Tom';
const array = [1,2,3];
const number = 100;
const iterableName = 'Tom'[Symbol.iterator]();
for(const letter of iterableName){
console.log(letter);
}
}
デバッガを確認すると、StringIteratorという型を生成しているようです。
functionを実行してみましょう。このようなログが出力されます。
とはいえもともと、Stringはイテラブルオブジェクトなので、Symbol.iteratorメソッドを実装しなくてもイテラブルオブジェクトとして処理できます。
function myFunction3_24_05(){
const name = 'Tom';
for(const letter of name){
console.log(letter); //T, o, m
}
}
最後に、イテラブルオブジェクトではないオブジェクトです。
たとえば、fileイテレータオブジェクトは、イテラブルオブジェクトではありません。
考察してみましたが、やはり、while文とhasNext()とnext()を使って処理するしかなさそうですね。
function myFunction3_24_06() {
const folder = DriveApp.getFolderById('ID');
const files = folder.getFiles();
Logger.log(files); //FileIterator
console.log(files.length); //undefined
//TypeError: files is not iterable
// for (const file of files) {
// console.log(file.getName());
// }
//実装できない
// const iterableFiles = files[Symbol.iterator](); //TypeError: files[Symbol.iterator] is not a function
//この処理をクラス化するしかないのかな?
while (files.hasNext()) {
const file = files.next();
console.log(file.getName());
}
}
まとめ
以上で、「argumentsへのリファレンスは変数に保存する」をお届けしました。
余白があったので、prototypeとSymbol.iteratorに軽く触れてみました。
また、どこかで回収したいと思います。
第3回目の輪読会は、ここまでです。
次回輪読会をお楽しみに。
参考資料
このシリーズの目次
- [EffectiveJavaScript輪読会3]関数、メソッド、コンストラクタの、呼び出しの違いを理解する
- [EffectiveJavaScript輪読会3]高階関数を快適に使えるようにしよう
- [EffectiveJavaScript輪読会3]カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう
- [EffectiveJavaScript輪読会3]いくつでも引数をとれる関数を呼び出すにはapplyを使おう
- [EffectiveJavaScript輪読会3]可変長引数関数を作るには、argumentsを使う
- [EffectiveJavaScript輪読会3]argumentsオブジェクトを書き換えない
- [EffectiveJavaScript輪読会3]argumentsへのリファレンスは変数に保存する