
Bom dia amigos!
Esta nota não tem nenhum valor prático particular. Por outro lado, ele explora alguns dos recursos "limítrofes" do JavaScript que você pode achar interessantes.
O Guia de estilo JavaScript do Goggle aconselha você a priorizar para-de sempre que possível.
O Guia de Estilo JavaScript do Airbnb desencoraja o uso de iteradores. Em vez de loops for-in e for-of, você deve usar funções de ordem superior, como map (), every (), filter (), find (), findIndex (), reduce (), some () para iterar em arrays e Object .keys (), Object.values (), Object.entries () para iterar em matrizes de objetos. Mais sobre isso mais tarde.
Vamos voltar ao Google. O que significa “onde possível”?
Vejamos alguns exemplos.
Digamos que temos uma matriz como esta:
const users = ["John", "Jane", "Bob", "Alice"];
E queremos enviar os valores de seus elementos para o console. Como vamos fazer isso?
//
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));
Tudo funciona muito bem sem nenhum esforço extra de nossa parte.
Agora, suponha que temos um objeto como este:
const person = {
name: "John",
age: 30,
job: "developer",
};
E queremos fazer o mesmo.
// 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]));
Veja a diferença? Temos que recorrer a truques adicionais, que consistem em converter um objeto em um array de uma forma ou de outra, porque:
for (const value of person) {
log(value); // TypeError: person is not iterable
}
O que essa exceção nos diz? Diz que o objeto "pessoa", entretanto, como qualquer outro objeto, não é uma entidade iterável ou, como dizem, uma entidade iterável (iterável).
Sobre quais iteráveis e iteradores estão muito bem escritos nesta seção do Tutorial de JavaScript moderno. Com sua permissão, não vou copiar e colar. No entanto, eu recomendo fortemente que você gaste 20 minutos lendo-o. Caso contrário, uma discussão mais aprofundada não fará muito sentido para você.
Digamos que não gostamos do fato de os objetos não serem iteráveis e queremos mudar isso. Como vamos fazer isso?
Aqui está um exemplo dado por Ilya Kantor:
//
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
// !
Essencialmente, o exemplo fornecido é um gerador criado com um iterador. Mas voltando ao nosso objetivo. Uma função para transformar um objeto regular em um iterável pode ter a seguinte aparência:
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 }
),
});
};
Nós verificamos:
makeIterator(person);
for (const value of person) {
log(value); // John 30 developer
}
Aconteceu! Agora podemos converter facilmente esse objeto em uma matriz e também obter o número de seus elementos por meio da propriedade "size":
const arr = Array.from(person);
log(arr); // ["John", 30, "developer"]
log(arr.size); // 3
Podemos simplificar nosso código de função usando um gerador em vez de um iterador:
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
Podemos usar o método "próximo" imediatamente após criar o iterável?
log(person.next().value); // TypeError: person.next is not a function
Para termos essa oportunidade, devemos primeiro chamar o Symbol.iterator do objeto:
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
É importante notar que, se você precisa criar um objeto iterável, é melhor definir imediatamente Symbol.iterator nele. Usando nosso objeto como exemplo:
const person = {
name: "John",
age: 30,
job: "developer",
[Symbol.iterator]: function* () {
for (const i in this) {
yield this[i];
}
},
};
Se movendo. Onde ir? Em metaprogramação. E se quisermos obter os valores das propriedades do objeto por índice, como em arrays? E se quisermos que certas propriedades de um objeto sejam imutáveis. Vamos implementar esse comportamento usando um proxy . Por que usar um proxy? Bem, apenas porque podemos:
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
Que conclusões podemos tirar de tudo isso? Você pode, é claro, criar um objeto iterável por conta própria (é JavaScript, baby), mas a questão é por quê. Concordamos com o Guia do Airbnb que existem métodos nativos mais do que suficientes para resolver toda a gama de tarefas relacionadas à iteração de chaves e valores de objetos, não há necessidade de “reinventar a roda”. O guia do Google pode ser esclarecido pelo fato de que o loop for-of deve ser preferido para arrays e arrays de objetos, para objetos como tal, você pode usar o loop for-in, mas melhor - funções integradas.
Espero que você tenha encontrado algo interessante para você. Obrigado pela atenção.