iterablesに関するメモ





良い一日、友達!



このメモには特に実用的な価値はありません。一方、JavaScriptの「境界線」機能のいくつかについて説明します。



GoggleのJavaScriptスタイルガイドは、可能な限りfor-ofループを優先するようにアドバイスしています



Airbnb JavaScriptスタイルガイドでは、イテレーターの使用を推奨していません。 for-inおよびfor-ofループの代わりに、map()、every()、filter()、find()、findIndex()、reduce()、some()などの高次関数を使用して配列を反復処理する必要があります。 .keys()、Object.values()、Object.entries()は、オブジェクトの配列を反復処理します。これについては後で詳しく説明します。



Googleに戻りましょう。「可能な場合」とはどういう意味ですか?



いくつかの例を見てみましょう。



次のような配列があるとしましょう。



const users = ["John", "Jane", "Bob", "Alice"];


そして、その要素の値をコンソールに出力したいと思います。これをどのように行うのですか?



//  
log = (value) => console.log(value);

// for
for (let i = 0; i < users.length; i++) {
  log(users[i]); // John Jane Bob Alice
}

// for-in
for (const item in users) {
  log(users[item]);
}

// for-of
for (const item of users) {
  log(item);
}

// forEach()
users.forEach((item) => log(item));

// map()
//   -   
//       forEach()
users.map((item) => log(item));


私たちの側で余分な努力をしなくても、すべてがうまく機能します。



ここで、次のようなオブジェクトがあるとします。



const person = {
  name: "John",
  age: 30,
  job: "developer",
};


そして、私たちは同じことをしたいと思っています。



// for
for (let i = 0; i < Object.keys(person).length; i++) {
  log(Object.values(person)[i]); // John 30 developer
}

// for-in
for (const i in person) {
  log(person[i]);
}

// for-of & Object.values()
for (const i of Object.values(person)) {
  log(i);
}

// Object.keys() & forEach()
Object.keys(person).forEach((i) => log(person[i]));

// Object.values() & forEach()
Object.values(person).forEach((i) => log(i));

// Object.entries() & forEach()
Object.entries(person).forEach((i) => log(i[1]));


違いを見ます?次の理由から、オブジェクトを何らかの方法で配列に変換するという追加のトリックに頼る必要があります。



  for (const value of person) {
    log(value); // TypeError: person is not iterable
  }


この例外は私たちに何を伝えますか?ただし、オブジェクト「人」は、他のオブジェクトと同様に、反復可能ではなく、彼らが言うように、反復可能(反復可能)エンティティではないことを示しています。最新のJavaScriptチュートリアルのこのセクションでは、



イテラブルとイテレーターが非常によく記述されていることについて説明します。あなたの許可を得て、私はコピー&ペーストしません。ただし、20分かけて読むことを強くお勧めします。そうでなければ、それ以上のプレゼンテーションはあなたにとってあまり意味がありません。



オブジェクトが反復可能ではないことが気に入らず、それを変更したいとします。これをどのように行うのですか?



IlyaKantorによる例を次に示します。



//   
const range = {
  from: 1,
  to: 5,
};

//    Symbol.iterator
range[Symbol.iterator] = function () {
  return {
    //  
    current: this.from,
    //  
    last: this.to,

    //    
    next() {
      //     
      if (this.current <= this.last) {
        //   ,    
        return { done: false, value: this.current++ };
      } else {
        //    ,      
        return { done: true };
      }
    },
  };
};

for (const num of range) log(num); // 1 2 3 4 5
// !


基本的に、提供されている例は、イテレーターで作成されたジェネレーターです。しかし、私たちの目的に戻ります。通常のオブジェクトを反復可能に変換する関数は、次のようになります。



const makeIterator = (obj) => {
  //    "size",   "length" 
  Object.defineProperty(obj, "size", {
    value: Object.keys(obj).length,
  });

  obj[Symbol.iterator] = (
    i = 0,
    values = Object.values(obj)
  ) => ({
    next: () => (
      i < obj.size
        ? { done: false, value: values[i++] }
        : { done: true }
    ),
  });
};


チェックします:



makeIterator(person);

for (const value of person) {
  log(value); // John 30 developer
}


起こりました!これで、このようなオブジェクトを簡単に配列に変換したり、「size」プロパティを使用してその要素の数を取得したりできます。



const arr = Array.from(person);

log(arr); // ["John", 30, "developer"]

log(arr.size); // 3


イテレーターの代わりにジェネレーターを使用することで、関数コードを簡略化できます。



const makeGenerator = (obj) => {
  //   
  //   
  Object.defineProperty(obj, "isAdult", {
    value: obj["age"] > 18,
  });

  obj[Symbol.iterator] = function* () {
    for (const i in this) {
      yield this[i];
    }
  };
};

makeGenerator(person);

for (const value of person) {
  log(value); // John 30 developer
}

const arr = [...person];

log(arr); // ["John", 30, "developer"]

log(person.isAdult); // true


iterableを作成した直後に「next」メソッドを使用できますか?



log(person.next().value); // TypeError: person.next is not a function


このような機会を得るには、最初にオブジェクトのSymbol.iteratorを呼び出す必要があります。



const iterablePerson = person[Symbol.iterator]();

log(iterablePerson.next()); // { value: "John", done: false }
log(iterablePerson.next().value); // 30
log(iterablePerson.next().value); // developer
log(iterablePerson.next().done); // true


反復可能なオブジェクトを作成する必要がある場合は、すぐにSymbol.iteratorを定義することをお勧めします。例としてオブジェクトを使用します。



const person = {
  name: "John",
  age: 30,
  job: "developer",

  [Symbol.iterator]: function* () {
    for (const i in this) {
      yield this[i];
    }
  },
};


先に進みます。どこへ行く?メタプログラミングに。配列のように、インデックスによってオブジェクトプロパティの値を取得したい場合はどうなりますか?また、オブジェクトの特定のプロパティを不変にしたい場合はどうでしょうか。プロキシを使用してこの動作を実装しましょう。なぜプロキシを使用するのですか?まあ、私たちができるという理由だけで:



const makeProxy = (obj, values = Object.values(obj)) =>
  new Proxy(obj, {
    get(target, key) {
      //     
      key = parseInt(key, 10);
      //    ,      0    
      if (key !== NaN && key >= 0 && key < target.size) {
        //   
        return values[key];
      } else {
        //  ,    
        throw new Error("no such property");
      }
    },
    set(target, prop, value) {
      //     "name"   "age"
      if (prop === "name" || prop === "age") {
        //  
        throw new Error(`this property can't be changed`);
      } else {
        //     
        target[prop] = value;
        return true;
      }
    },
  });

const proxyPerson = makeProxy(person);
//  
log(proxyPerson[0]); // John
//    
log(proxyPerson[2]); // Error: no such property
//   
log((proxyPerson[2] = "coding")); // true
//    
log((proxyPerson.name = "Bob")); // Error: this property can't be changed


これらすべてからどのような結論を導き出すことができますか?もちろん、自分で反復可能なオブジェクト(JavaScript、baby)を作成することもできますが、問題はその理由です。オブジェクトのキーと値の反復に関連するタスクの全範囲を解決するのに十分なネイティブメソッドがあり、「ホイールを再発明」する必要がないというAirbnbガイドに同意します。Googleのガイドは、for-ofループが配列およびオブジェクトの配列に優先される必要があるという事実によって明確になります。オブジェクト自体の場合、for-inループを使用できますが、より優れた組み込み関数です。



あなたがあなた自身のために何か面白いものを見つけたことを望みます。清聴ありがとうございました。



All Articles