JS Понимание итерационных протоколов

EcmaScript 6 предоставляет протоколы итераций, которые состоят из Iterator и Iterable. С моей точки зрения, в некоторых случаях это может быть очень полезно, поэтому в этой статье мы рассмотрим эту тему.

Наше путешествие:

  1. Где можно использовать итерационные протоколы
  2. Понимание протокола итератора
  3. Итерационный протокол
  4. Определение цикла для неперечислимых свойств

Где уже используются протоколы итераций

Я хочу начать наше путешествие с темы «Где уже используются протоколы итерации», потому что в этом случае вы поймете, зачем вам это нужно.

Массив уже встроен итерируемый. (не только Array, но также String, Map и Set, но мы будем использовать Array для наших примеров).

Во-вторых, вам нужно знать, «где это использовать», и ответ на этот вопрос очень прост:
Объекты, реализующие протокол Iterable, могут использоваться в for..of и с помощью оператора распространения.

for… of пример цикла:

let numbers = [1, 2, 3, 4];
for(let number of numbers) {
  console.log(number);
}

Пример оператора распространения:

let numbers = [1, 2, 3, 4];
console.log(...numbers);

Дополнительную информацию об операторе for… of и операторе распространения можно найти в MDN:

  1. цикл for..of
  2. Операторы распространения

Знания о том, что объект, который реализует протоколы итераций, можно использовать в for..of и с оператором распространения будет достаточно. теперь.

Понимание протокола итератора

Для начала попробуем разобраться, что в нашем случае означает протокол.
Когда объект реализует какой-либо протокол, это означает, что существует соглашение, которое гласит:

«Чтобы использовать этот протокол, ваш объект должен реализовать следующую функциональность ... со следующими правилами ...».

Теперь мы готовы реализовать протокол Iterator.

Протокол итератора - i n включает следующие соглашения (правила):

1. Объект должен реализовывать метод .next ().

2. Метод .next () должен возвращать объект, который будет содержать следующие свойства:

`value` - будет единичная единица от итерации

`done` - Имеет значение true, если итератор прошел конец повторяемой последовательности. В объекте, который содержит done, значение true будет неопределенным.

Пример:

let objWithIteratorProtocol = {
  users: [
    {id: 1, username: 'Oleh'},
    {id: 2, username: 'Erik'},
    {id: 3, username: 'John'}
  ],
  nextIndex: 0,
  next: function() {
    return this.nextIndex < this.users.length
      ? {value: this.users[this.nextIndex++], done: false}
      : {done: true}
  }
};
console.log(objWithIteratorProtocol.next()); // { value: { id: 1, username: 'Oleh' }, done: false }
console.log(objWithIteratorProtocol.next()); // { value: { id: 2, username: 'Erik' }, done: false }
console.log(objWithIteratorProtocol.next()); // { value: { id: 3, username: 'John' }, done: false }
console.log(objWithIteratorProtocol.next().done); // true

Итерационный протокол

Итерируемый протокол - включает следующие соглашения:

1. Объект, реализующий протокол Iterable, должен иметь свойство Symbol.iterator.

2. Symbol.iterator должен возвращать итератор.

Пример:

let objWithItarableProtocol = {
  users: [
    {id: 1, username: 'Oleh'},
    {id: 2, username: 'Erik'},
    {id: 3, username: 'John'}
  ],
  nextIndex: 0,
  [Symbol.iterator]: function() {
    return {
      array: this.users,
      nextIndex: this.nextIndex,
      next: function() {
        return this.nextIndex < this.array.length
          ? {value: this.array[this.nextIndex++], done: false}
          : {done: true};
      }
    };
  }
};

Использование:

  1. Мы можем использовать итератор объекта (как это было в примере с протоколом итератора), но мы должны обрабатывать каждую итерацию самостоятельно:
let iterator = objWithItarableProtocol[Symbol.iterator]();
console.log(iterator.next()); 
console.log(iterator.next()); 
console.log(iterator.next()); 
console.log(iterator.next().done);
/*
Result:
=======
{ value: { id: 1, username: 'Oleh' }, done: false }
{ value: { id: 2, username: 'Erik' }, done: false }
{ value: { id: 3, username: 'John' }, done: false }
true
*/

2. Мы можем использовать его в цикле for… of (цикл for… of возвращается в каждом значении итерации и будет завершен, когда Метод .next () возвращает объект со свойством done, установленным в true):

for(let user of objWithItarableProtocol) {
  console.log(user);
}
/*
Result:
=======
{ id: 1, username: 'Oleh' }
{ id: 2, username: 'Erik' }
{ id: 3, username: 'John' }
*/

3. На мой взгляд, самый простой способ - использовать оператор распространения:

console.log(...objWithItarableProtocol);
/*
Result:
=======
{ id: 1, username: 'Oleh' } { id: 2, username: 'Erik' } { id: 3, username: 'John' }
*/

Но, честно говоря, я немного беспокоился о том, как это будет работать в этом случае:

let otherObject = {
  val: 'Some Value'
}
console.log({...otherObject, ...objWithItarableProtocol});

Но похоже, что все это хорошо обрабатывается JavaScript, поэтому вот результат:

{ val: 'Some Value',
  users:
   [ { id: 1, username: 'Oleh' },
     { id: 2, username: 'Erik' },
     { id: 3, username: 'John' } ],
  nextIndex: 0,
  [Symbol(Symbol.iterator)]: [Function: [Symbol.iterator]] }

В итоге: чтобы использовать функции языка JavaScript, связанные с итерацией, мы должны реализовать протоколы итераций (соглашения).

Определение цикла для неперечислимых свойств

Хорошо, вот еще один пример, который может дать вам несколько интересных идей.

Иногда мы используем стороннюю библиотеку и связанные с ней объекты. Чтобы проверить, какой объект находится внутри, мы можем использовать конструкцию цикла for… in, но он не покажет, что находится под капотом (неперечислимые свойства). Таким образом, мы можем организовать это с помощью итерационных протоколов.

Идея состоит в использовании для перечислимых свойств цикла for… in и для неперечислимого цикла for… of.

У нас есть объект с двумя свойствами, одно перечислимое, а второе - нет:

let objWithNotEnumerableProps = {
  name: 'Object Name'
};
Object.defineProperty(objWithNotEnumerableProps, 'nonEnumerable', {
  enumerable: false,
  configurable: false,
  writable: false,
  value: 'Non Enumerable Value'
});
for(let prop in objWithNotEnumerableProps) {
    console.log(prop); // "name"
    console.log(objWithNotEnumerableProps[prop]); // "Object Name"
}

Поскольку цикл for… in будет работать как есть, мы должны организовать логику только для цикла for… of. :

objWithNotEnumerableProps[Symbol.iterator] = function*() {
  let props = Object.getOwnPropertyNames(this);
  for(let index in props) {
    if(!this.propertyIsEnumerable(props[index])) yield props[index];
  }
}
for(let prop of objWithNotEnumerableProps) {
  console.log(prop); // "nonEnumerable"
  console.log(objWithNotEnumerableProps[prop]); // "Non Enumerable Value"
}

… И еще одно: мы можем использовать метод Object.keys для перечислимых свойств и оператор распространения для неперечисляемых:

console.log(Object.keys(objWithNotEnumerableProps)); // ["name"]
console.log([...objWithNotEnumerableProps]); // ["nonEnumerable"]

Заключение

Спасибо, ребята, что прочитали. Надеюсь, вам понравилось, и вы узнали что-то новое, связанное с JavaScript. Пожалуйста, подпишитесь и нажмите кнопку «Хлопок», если вам понравилась эта статья.