JavaScript: explorando objetos

O material, cuja tradução publicamos hoje, é dedicado ao estudo de objetos - uma das principais essências do JavaScript. Ele foi desenvolvido principalmente para desenvolvedores iniciantes que desejam otimizar seus conhecimentos sobre objetos.



Objetos em JavaScript são coleções dinâmicas de propriedades que, além disso, contêm uma propriedade "oculta" que é um protótipo do objeto. Propriedades de objetos são caracterizadas por chaves e valores. Vamos começar a conversa sobre objetos JS com chaves.

Chaves de propriedade do objeto


A chave de propriedade do objeto é uma sequência exclusiva. Você pode usar dois métodos para acessar propriedades: acessando-as por um período e especificando a chave do objeto entre colchetes. Ao acessar propriedades por meio de um ponto, a chave deve ser um identificador JavaScript válido. Considere um exemplo:

let obj = {  message : "A message" } obj.message //"A message" obj["message"] //"A message" 

Ao tentar acessar uma propriedade inexistente de um objeto, uma mensagem de erro não aparecerá, mas o valor undefined será retornado:

 obj.otherProperty //undefined 

Ao usar colchetes para acessar propriedades, você pode usar chaves que não são identificadores JavaScript válidos (por exemplo, a chave pode ser uma sequência que contém espaços). Eles podem ter qualquer valor que possa ser convertido em uma sequência:

 let french = {}; french["merci beaucoup"] = "thank you very much"; french["merci beaucoup"]; //"thank you very much" 

Se valores não-string forem usados ​​como chaves, eles serão automaticamente convertidos em strings (usando, se possível, o toString() ):

 et obj = {}; //Number obj[1] = "Number 1"; obj[1] === obj["1"]; //true //Object let number1 = { toString : function() { return "1"; } } obj[number1] === obj["1"]; //true 

Neste exemplo, o objeto number1 é usado como chave. Ao tentar acessar uma propriedade, ela é convertida na linha 1 e o resultado dessa conversão é usado como chave.

Valores da propriedade do objeto


As propriedades do objeto podem ser valores, objetos ou funções primitivos.

Objeto como um valor de propriedade do objeto


Objetos podem ser colocados em outros objetos. Considere um exemplo :

 let book = { title : "The Good Parts", author : {   firstName : "Douglas",   lastName : "Crockford" } } book.author.firstName; //"Douglas" 

Uma abordagem semelhante pode ser usada para criar namespaces:

 let app = {}; app.authorService = { getAuthors : function() {} }; app.bookService = { getBooks : function() {} }; 

▍ Funcionar como um valor de propriedade do objeto


Quando uma função é usada como um valor de propriedade do objeto, ela geralmente se torna um método de objeto. Dentro do método, para acessar o objeto atual, use a this .

Essa palavra-chave, no entanto, pode ter significados diferentes, dependendo de como a função foi chamada. Aqui você pode ler sobre situações em que this perde contexto.

A natureza dinâmica dos objetos


Objetos em JavaScript, por natureza, são entidades dinâmicas. Você pode adicionar propriedades a qualquer momento, o mesmo vale para a exclusão de propriedades:

 let obj = {}; obj.message = "This is a message"; //   obj.otherMessage = "A new message"; //    delete obj.otherMessage; //  

Objetos como matrizes associativas


Os objetos podem ser considerados como matrizes associativas. Chaves de matriz associativas são os nomes de propriedade do objeto. Para acessar a chave, não é necessário examinar todas as propriedades, ou seja, a operação de acesso à chave de uma matriz associativa baseada em um objeto é realizada em O (1).

Protótipos de Objetos


Os objetos têm um link "oculto", __proto__ , apontando para um objeto protótipo do qual o objeto herda propriedades.

Por exemplo, um objeto criado usando um literal de objeto tem um link para Object.prototype :

 var obj = {}; obj.__proto__ === Object.prototype; //true 

▍ Objetos vazios


Como acabamos de ver, o objeto "vazio", {} , na verdade não é tão vazio, pois contém uma referência ao Object.prototype . Para criar um objeto verdadeiramente vazio, você precisa usar a seguinte construção:

 Object.create(null) 

Graças a isso, um objeto sem protótipo será criado. Esses objetos são geralmente usados ​​para criar matrizes associativas.

Chain Cadeia de protótipo


Objetos protótipos podem ter seus próprios protótipos. Se você tentar acessar uma propriedade de um objeto que não está nele, o JavaScript tentará encontrar essa propriedade no protótipo desse objeto e, se a propriedade desejada não estiver lá, será feita uma tentativa de encontrá-la no protótipo do protótipo. Isso continuará até que a propriedade desejada seja encontrada ou até o fim da cadeia de protótipos.

Valores de tipo primitivo e wrappers de objeto


O JavaScript permite que você trabalhe com os valores de tipos primitivos como objetos, no sentido em que a linguagem permite acessar suas propriedades e métodos.

 (1.23).toFixed(1); //"1.2" "text".toUpperCase(); //"TEXT" true.toString(); //"true" 

Além disso, é claro, os valores dos tipos primitivos não são objetos.

Para organizar o acesso às “propriedades” dos valores dos tipos primitivos, o JavaScript, se necessário, cria objetos de wrapper que, depois de se tornarem desnecessários, são destruídos. O processo de criação e destruição de objetos wrapper é otimizado pelo mecanismo JS.

Os wrappers de objetos têm valores de tipos numérico, de sequência e lógico. Objetos dos tipos correspondentes são representados pelas funções de construtor Number , String e Boolean .

Protótipos incorporados


Os objetos Number herdam propriedades e métodos do protótipo Number.prototype , que é o descendente de Object.prototype :

 var no = 1; no.__proto__ === Number.prototype; //true no.__proto__.__proto__ === Object.prototype; //true 

O protótipo de objetos string é String.prototype . O protótipo de objetos booleanos é Boolean.prototype . O protótipo de matrizes (que também são objetos) é Array.prototype .

Funções em JavaScript também são objetos que possuem um protótipo Function.prototype . Funções têm métodos como bind() , apply() e call() .

Todos os objetos, funções e objetos que representam valores de tipo primitivo (exceto valores null e undefined ) herdam propriedades e métodos do Object.prototype . Isso leva ao fato de que, por exemplo, todos eles têm um toString() .

Estendendo objetos incorporados com polyfills


O JavaScript facilita a extensão de objetos incorporados com novos recursos usando os chamados polyfills. Um polyfill é um pedaço de código que implementa recursos que não são suportados por nenhum navegador.

▍Uso de polyfills


Por exemplo, há um polyfill para o método Object.assign() . Permite adicionar uma nova função ao Object se ela não estiver disponível.

O mesmo se aplica ao polyfill Array.from() , que, se o método from() não estiver no objeto Array , o equipará com esse método.

F Polifill e protótipos


Com a ajuda de polyfills, novos métodos podem ser adicionados aos protótipos de objetos. Por exemplo, o polyfill para String.prototype.trim() permite equipar todos os objetos de string com o método trim() :

 let text = "   A text "; text.trim(); //"A text" 

O polyfill para Array.prototype.find() permite equipar todas as matrizes com o método find() . O polyfill para Array.prototype.findIndex() funciona de maneira semelhante:

 let arr = ["A", "B", "C", "D", "E"]; arr.indexOf("C"); //2 

Herança única


O comando Object.create() permite criar novos objetos com um determinado objeto de protótipo. Este comando é usado no JavaScript para implementar um único mecanismo de herança. Considere um exemplo :

 let bookPrototype = { getFullTitle : function(){   return this.title + " by " + this.author; } } let book = Object.create(bookPrototype); book.title = "JavaScript: The Good Parts"; book.author = "Douglas Crockford"; book.getFullTitle();//JavaScript: The Good Parts by Douglas Crockford 

Herança múltipla


O comando Object.assign() copia propriedades de um ou mais objetos para o objeto de destino. Pode ser usado para implementar vários esquemas de herança. Aqui está um exemplo :

 let authorDataService = { getAuthors : function() {} }; let bookDataService = { getBooks : function() {} }; let userDataService = { getUsers : function() {} }; let dataService = Object.assign({}, authorDataService, bookDataService, userDataService ); dataService.getAuthors(); dataService.getBooks(); dataService.getUsers(); 

Objetos imutáveis


O comando Object.freeze() permite congelar um objeto. Você não pode adicionar novas propriedades a esse objeto. As propriedades não podem ser excluídas, nem seus valores podem ser alterados. Ao usar este comando, um objeto se torna imutável ou imutável:

 "use strict"; let book = Object.freeze({ title : "Functional-Light JavaScript", author : "Kyle Simpson" }); book.title = "Other title";//: Cannot assign to read only property 'title' 

O comando Object.freeze() executa o chamado "congelamento superficial" dos objetos. Isso significa que objetos aninhados em um objeto "congelado" podem ser modificados. Para realizar um "congelamento profundo" de um objeto, é necessário "congelar" recursivamente todas as suas propriedades.

Clonando Objetos


Para criar clones (cópias) de objetos, você pode usar o comando Object.assign() :

 let book = Object.freeze({ title : "JavaScript Allongé", author : "Reginald Braithwaite" }); let clone = Object.assign({}, book); 

Este comando executa cópia superficial de objetos, ou seja, copia apenas propriedades de nível superior. Objetos aninhados acabam sendo comuns para objetos originais e suas cópias.

Literal do objeto


Literais de objeto fornecem aos desenvolvedores uma maneira simples e direta de criar objetos:

 let timer = { fn : null, start : function(callback) { this.fn = callback; }, stop : function() {}, } 

No entanto, esse método de criação de objetos tem desvantagens. Em particular, com essa abordagem, todas as propriedades do objeto estão disponíveis ao público, os métodos do objeto podem ser redefinidos, eles não podem ser usados ​​para criar novas instâncias dos mesmos objetos:

 timer.fn;//null timer.start = function() { console.log("New implementation"); } 

Método Object.create ()


Os dois problemas mencionados acima podem ser resolvidos através do uso conjunto dos métodos Object.create() e Object.freeze() .

Aplicamos essa técnica ao nosso exemplo anterior. Primeiro, crie um protótipo congelado timerPrototype que contenha todos os métodos necessários para várias instâncias do objeto. Depois disso, crie um objeto sucessor do timerPrototype :

 let timerPrototype = Object.freeze({ start : function() {}, stop : function() {} }); let timer = Object.create(timerPrototype); timer.__proto__ === timerPrototype; //true 

Se o protótipo estiver protegido contra alterações, o objeto que é seu herdeiro não poderá alterar as propriedades definidas no protótipo. Agora, os métodos start() e stop() não podem ser substituídos:

 "use strict"; timer.start = function() { console.log("New implementation"); } //: Cannot assign to read only property 'start' of object 

A Object.create(timerPrototype) pode ser usada para criar vários objetos com o mesmo protótipo.

Função construtora


O JavaScript possui as chamadas funções construtoras, que são "açúcar sintático" para executar as etapas acima para criar novos objetos. Considere um exemplo :

 function Timer(callback){ this.fn = callback; } Timer.prototype = { start : function() {}, stop : function() {} } function getTodos() {} let timer = new Timer(getTodos); 

Você pode usar qualquer função como construtor. O construtor é chamado usando a new palavra-chave. Um objeto criado usando uma função de construtor chamada FunctionConstructor receberá um protótipo FunctionConstructor.prototype :

 let timer = new Timer(); timer.__proto__ === Timer.prototype; 

Aqui, para impedir uma alteração no protótipo, novamente, você pode congelar o protótipo:

 Timer.prototype = Object.freeze({ start : function() {}, stop : function() {} }); 

▍ Palavra-chave novo


Quando um comando do formulário new Timer() é executado, as mesmas ações são executadas como a função newTimer() executa abaixo:

 function newTimer(){ let newObj = Object.create(Timer.prototype); let returnObj = Timer.call(newObj, arguments); if(returnObj) return returnObj;   return newObj; } 

Um novo objeto é criado aqui, cujo protótipo é Timer.prototype . Em seguida, a função Timer é chamada, configurando os campos para o novo objeto.

Palavra-chave da classe


O ECMAScript 2015 introduziu uma nova maneira de executar as ações acima, que é outro lote de "açúcar sintático". Estamos falando da palavra-chave da class e das construções relacionadas a ela associadas. Considere um exemplo :

 class Timer{ constructor(callback){   this.fn = callback; } start() {} stop() {} } Object.freeze(Timer.prototype); 

Um objeto criado usando a palavra-chave da class base em uma classe chamada ClassName terá o protótipo ClassName.prototype . Ao criar um objeto com base em uma classe, use a new palavra-chave:

 let timer= new Timer(); timer.__proto__ === Timer.prototype; 

O uso de classes não torna os protótipos imutáveis. Se necessário, eles terão que ser "congelados" da mesma maneira que já fizemos:

 Object.freeze(Timer.prototype); 

Herança baseada em protótipo


No JavaScript, os objetos herdam propriedades e métodos de outros objetos. As funções e classes do construtor são "açúcar sintático" para criar objetos protótipos que contêm todos os métodos necessários. Utilizando-os, são criados novos objetos que são herdeiros do protótipo, cujas propriedades são específicas para uma instância específica, são definidas usando a função construtora ou os mecanismos de classe.

Seria bom se as funções e classes do construtor pudessem automaticamente tornar os protótipos imutáveis.

Os pontos fortes da herança do protótipo são a economia de memória. O fato é que um protótipo é criado apenas uma vez, após o qual todos os objetos criados em sua base o utilizam.

Problem O problema da falta de mecanismos de encapsulamento embutidos


O modelo de herança do protótipo não usa a separação das propriedades dos objetos em privada e pública. Todas as propriedades dos objetos estão disponíveis ao público.

Por exemplo, o comando Object.keys() retorna uma matriz que contém todas as chaves de propriedade do objeto. Ele pode ser usado para iterar sobre todas as propriedades de um objeto:

 function logProperty(name){ console.log(name); //  console.log(obj[name]); //  } Object.keys(obj).forEach(logProperty); 

Há um padrão que imita propriedades privadas, contando com o fato de que os desenvolvedores não acessarão aquelas propriedades cujos nomes começam com um sublinhado ( _ ):

 class Timer{ constructor(callback){   this._fn = callback;   this._timerId = 0; } } 

Recursos de fábrica


Objetos encapsulados em JavaScript podem ser criados usando funções de fábrica. É assim:

 function TodoStore(callback){   let fn = callback;     function start() {},   function stop() {}     return Object.freeze({      start,      stop   }); } 

Aqui a variável fn é privada. Somente os métodos start() e stop() estão disponíveis publicamente. Esses métodos não podem ser modificados externamente. A palavra-chave this não é usada aqui, portanto, ao usar esse método de criação de objetos, o problema de perder this contexto é irrelevante.

O comando return usa um literal de objeto contendo apenas funções. Além disso, essas funções são declaradas em encerramento e compartilham um estado comum. Para congelar uma API pública de um objeto, o comando Object.freeze() já conhecido é Object.freeze() .

Aqui, nos exemplos, usamos o objeto Timer . Neste material, você pode encontrar sua implementação completa.

Sumário


Em JavaScript, os valores de tipos primitivos, objetos comuns e funções são tratados como objetos. Os objetos têm uma natureza dinâmica, podem ser usados ​​como matrizes associativas. Objetos são herdeiros de outros objetos. As funções e classes do construtor são "açúcar sintático", pois permitem criar objetos com base em protótipos. Você pode usar o método Object.create() para organizar herança única e Object.create() para organizar herança múltipla. Você pode usar as funções de fábrica para criar objetos encapsulados.

Caros leitores! Se você veio ao JavaScript de outros idiomas, conte-nos sobre o que você gosta ou não nos objetos JS, em comparação com a implementação de objetos nos idiomas que você já conhece.

Source: https://habr.com/ru/post/pt420615/


All Articles