[Разработка веб-сайтов, JavaScript, Программирование] Заметка о перебираемых объектах
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Доброго времени суток, друзья!
Данная заметка не имеет особой практической ценности. С другой стороны, в ней исследуется некоторые «пограничные» возможности 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);
// 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
}
О чем нам говорит это исключение? Оно говорит о том, что объект «person», впрочем, как и любой другой объект, не является итерируемым или, как еще говорят, итерируемой (перебираемой) сущностью.
О том, что такое перебираемые сущности и итераторы, очень хорошо написано в этом разделе Современного учебника по JavaScript. С вашего позволения, я не буду заниматься копипастой. Однако, настоятельно рекомендую потратить 20 минут на его прочтение. В противном случае, дальнейшее изложение не будет иметь для вас особого смысла.
Допустим, что нам не нравится, что объекты не являются итерируемыми, и мы хотим это изменить. Как нам это сделать?
Вот пример, приводимый Ильей Кантором:
// имеется такой объект
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
Можем ли мы использовать метод «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, детка), но вопрос в том, зачем это делать. Следует согласиться с Руководством от Airbnb в том, что нативных методов более чем достаточно для решения всего спектра задач, связанных с перебором ключей и значений объектов, нет необходимости «изобретать велосипед». Руководство же от Google можно уточнить тем, что цикл for-of следует предпочитать для массивов и массивов из объектов, для объектов же как таковых можно использовать цикл for-in, но лучше — встроенные функции.
Надеюсь, вы нашли для себя что-нибудь интересное. Благодарю за внимание.
===========
Источник:
habr.com
===========
Похожие новости:
- [Семантика, Программирование, Prolog, Бизнес-модели] Проектируем мульти-парадигменный язык программирования. Часть 3 — Обзор языков представления знаний
- [Промышленное программирование, Разработка робототехники, Программирование микроконтроллеров, Разработка для интернета вещей, Производство и разработка электроники] ModBus Slave RTU/ASCII без смс и регистрации
- [Python, Программирование, Git, GitHub, Учебный процесс в IT] 25 лучших репозиториев GitHub для разработчиков Python (перевод)
- [Разработка веб-сайтов, Фриланс, DevOps, Удалённая работа] Будет ли оплата труда привязана к местоположению в будущем
- [Разработка веб-сайтов, PHP, Управление сообществом, Карьера в IT-индустрии] Как обстоят дела с PHP в Краснодаре (и не только)
- [Разработка веб-сайтов, JavaScript, Программирование] Управление памятью в JavaScript (перевод)
- [Программирование, Kotlin, Управление продуктом] Kotlin: язык программирования как продукт
- [Разработка веб-сайтов, Google API, IT-компании] Систему Google reCAPTCHA раскритиковали за приватность
- [Программирование, Big Data, Машинное обучение] Как я регулярно улучшаю точность моделей обучения с 80% до 90+% (перевод)
- [Программирование, Go] Жизнь в одну строчку (перевод)
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_programmirovanie (Программирование), #_javascript, #_iterator, #_generator, #_proxy, #_razrabotka_vebsajtov (
Разработка веб-сайтов
), #_javascript, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:37
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Доброго времени суток, друзья! Данная заметка не имеет особой практической ценности. С другой стороны, в ней исследуется некоторые «пограничные» возможности 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); // 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 } О чем нам говорит это исключение? Оно говорит о том, что объект «person», впрочем, как и любой другой объект, не является итерируемым или, как еще говорят, итерируемой (перебираемой) сущностью. О том, что такое перебираемые сущности и итераторы, очень хорошо написано в этом разделе Современного учебника по JavaScript. С вашего позволения, я не буду заниматься копипастой. Однако, настоятельно рекомендую потратить 20 минут на его прочтение. В противном случае, дальнейшее изложение не будет иметь для вас особого смысла. Допустим, что нам не нравится, что объекты не являются итерируемыми, и мы хотим это изменить. Как нам это сделать? Вот пример, приводимый Ильей Кантором: // имеется такой объект
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 Можем ли мы использовать метод «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, детка), но вопрос в том, зачем это делать. Следует согласиться с Руководством от Airbnb в том, что нативных методов более чем достаточно для решения всего спектра задач, связанных с перебором ключей и значений объектов, нет необходимости «изобретать велосипед». Руководство же от Google можно уточнить тем, что цикл for-of следует предпочитать для массивов и массивов из объектов, для объектов же как таковых можно использовать цикл for-in, но лучше — встроенные функции. Надеюсь, вы нашли для себя что-нибудь интересное. Благодарю за внимание. =========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ), #_javascript, #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:37
Часовой пояс: UTC + 5