Русский
Русский
English
Статистика
Реклама

Заметка о перебираемых объектах



Доброго времени суток, друзья!

Данная заметка не имеет особой практической ценности. С другой стороны, в ней исследуется некоторые пограничные возможности JavaScript, которые могут показаться вам интересными.

Руководство по стилю JavaScript от Goggle советует отдавать предпочтение циклу for-of там, где это возможно.

Руководство по стилю JavaScript от Airbnb не рекомендует использовать итераторы. Вместо циклов for-in и for-of следует использовать функции высшего порядка, такие как map(), every(), filter(), find(), findIndex(), reduce(), some() для итерации по массивам и Object.keys(), Object.values(), Object.entries() для итерации по массивам из объектов. Об этом позже.

Вернемся к Google. Что означает там, где это возможно?

Рассмотрим парочку примеров.

Допустим, у нас есть такой массив:

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

И мы хотим вывести в консоль значения его элементов. Как нам это сделать?

// вспомогательная функцияlog = (value) => console.log(value);// forfor (let i = 0; i < users.length; i++) {  log(users[i]); // John Jane Bob Alice}// for-infor (const item in users) {  log(users[item]);}// for-offor (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",};

И мы хотим сделать тоже самое.

// forfor (let i = 0; i < Object.keys(person).length; i++) {  log(Object.values(person)[i]); // John 30 developer}// for-infor (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  }

О чем нам говорит это исключение? Оно говорит о том, что объект person, впрочем, как и любой другой объект, не является итерируемым или, как еще говорят, итерируемой (перебираемой) сущностью.

О том, что такое перебираемые сущности и итераторы, очень хорошо написано в этом разделе Современного учебника по JavaScript. С вашего позволения, я не буду заниматься копипастой. Однако, настоятельно рекомендую потратить 20 минут на его прочтение. В противном случае, дальнейшее изложение не будет иметь для вас особого смысла.

Допустим, что нам не нравится, что объекты не являются итерируемыми, и мы хотим это изменить. Как нам это сделать?

Вот пример, приводимый Ильей Кантором:

// имеется такой объектconst range = {  from: 1,  to: 5,};// добавляем ему свойство Symbol.iteratorrange[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

Можем ли мы использовать метод 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); // 30log(iterablePerson.next().value); // developerlog(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, детка), но вопрос в том, зачем это делать. Следует согласиться с Руководством от Airbnb в том, что нативных методов более чем достаточно для решения всего спектра задач, связанных с перебором ключей и значений объектов, нет необходимости изобретать велосипед. Руководство же от Google можно уточнить тем, что цикл for-of следует предпочитать для массивов и массивов из объектов, для объектов же как таковых можно использовать цикл for-in, но лучше встроенные функции.

Надеюсь, вы нашли для себя что-нибудь интересное. Благодарю за внимание.
Источник: habr.com
К списку статей
Опубликовано: 04.11.2020 16:22:09
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка веб-сайтов

Javascript

Программирование

Iterator

Generator

Proxy

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru