Metaprogramação JavaScript

Metaprogramação é um tipo de programação associada à criação de programas que geram outros programas como resultado de seu trabalho, ou programas que se alteram durante a execução. (Wikipedia)

Em uma linguagem mais simples, a metaprogramação no JavaScript pode ser considerada um mecanismo que permite analisar e alterar o programa em tempo real, dependendo de qualquer ação. E, provavelmente, você de alguma forma os usa ao escrever scripts todos os dias.


O JavaScript, por sua natureza, é uma linguagem dinâmica muito poderosa e permite que você escreva códigos flexíveis de maneira agradável:


/** *   save-    */ const comment = { authorId: 1, comment: '' }; for (let name in comment) { const pascalCasedName = name.slice(0, 1).toUpperCase() + name.slice(1); comment[`save${pascalCasedName}`] = function() { //   } } comment.saveAuthorId(); //  authorId comment.saveComment(); //  comment 

Código semelhante para a criação dinâmica de métodos em outros idiomas com muita frequência pode exigir sintaxe ou API especial para isso. Por exemplo, o PHP também é uma linguagem dinâmica, mas exigirá mais esforço:


 <?php class Comment { public $authorId; public $comment; public function __construct($authorId, $comment) { $this->authorId = $authorId; $this->comment = $comment; } //       public function __call($methodName, $arguments) { foreach (get_object_vars($this) as $fieldName => $fieldValue) { $saveMethodName = "save" . strtoupper($fieldName[0]) . substr($fieldName, 1); if ($methodName == $saveMethodName) { //   } } } } $comment = new Comment(1, ''); $comment->saveAuthorId(); //  authorId $comment->saveComment(); //  comment 

Além da sintaxe flexível, também temos várias funções úteis para escrever código dinâmico: Object.create, Object.defineProperty, Function.apply e muitas outras.


Considere-os com mais detalhes.


  1. Geração de código
  2. Trabalhar com funções
  3. Trabalhar com objetos
  4. API do Reflect
  5. Símbolos
  6. Proxy
  7. Conclusão

1. Geração de Código


A ferramenta padrão para a execução dinâmica de código é a função eval , que permite executar o código a partir da string passada:


 eval('alert("Hello, world")'); 

Infelizmente, eval tem muitas nuances:


  • se nosso código for escrito no modo estrito ('use strict'), as variáveis ​​declaradas dentro de eval não serão visíveis no código eval de chamada. Ao mesmo tempo, o próprio código dentro de eval sempre pode alterar variáveis ​​externas.
  • o código dentro de eval pode ser executado tanto no contexto global (se chamado através de window.eval) quanto no contexto da função na qual a chamada foi feita (se for apenas eval, sem janela).
  • problemas podem surgir devido à minificação de JS, quando nomes de variáveis ​​são substituídos por nomes mais curtos para reduzir o tamanho O código passado como uma string para avaliação geralmente não toca no minificador, por isso podemos começar a acessar variáveis ​​externas usando nomes antigos não minificados, o que levará a erros sutis.

Existe uma ótima alternativa para resolver esses problemas - nova função .


 const hello = new Function('name', 'alert("Hello, " + name)'); hello('') // alert("Hello, "); 

Ao contrário de eval, sempre podemos passar parâmetros explicitamente pelos argumentos de uma função e fornecer dinamicamente o contexto disso (via Function.apply ou Function.call ). Além disso, a função criada é sempre chamada no escopo global.


Antigamente, o eval era frequentemente usado para alterar dinamicamente o código, como O JavaScript tinha muito poucos mecanismos de reflexão e era impossível ficar sem avaliação. Mas no padrão da linguagem moderna, muito mais funcionalidade de alto nível apareceu e o eval agora é usado com muito menos frequência.


2. Trabalhe com funções


O JavaScript nos fornece muitas ferramentas excelentes para trabalhar dinamicamente com funções, permitindo obter várias informações sobre a função em tempo de execução e alterá-la:


  • Function.length - permite descobrir o número de argumentos de uma função:


     const func = function(name, surname) { console.log(`Hello, ${surname} ${name}`) }; console.log(func.length) // 2 

  • Function.apply e Function.call - permitem alterar dinamicamente o contexto desta função:


     const person = { name: '', introduce: function() { return ` ${this.name}`; } } person.introduce(); //   person.introduce.call({ name: '' }); //   

    Eles diferem um do outro apenas porque, em Function.apply, os argumentos para a função são servidos como uma matriz e em Function.call, separados por vírgulas. Esse recurso costumava ser usado antes para passar uma lista de argumentos para a função como uma matriz. Um exemplo comum é a função Math.max (por padrão, ela não pode funcionar com matrizes):


     Math.max.apply(null, [1, 2, 4, 3]); // 4 

    Com o advento do novo operador de propagação, você pode simplesmente escrever o seguinte:


     Math.max(...[1, 2, 4, 3]); // 4 

  • Function.bind - permite criar uma cópia de uma função a partir de uma existente, mas com um contexto diferente:


     const person = { name: '', introduce: function() { return ` ${this.name}`; } } person.introduce(); //   const introduceEgor = person.introduce.bind({ name: '' }); introduceEgor(); //   

  • Function.caller - permite que você obtenha a função de chamada. Não é recomendável usá-lo , pois está ausente no padrão do idioma e não funcionará no modo estrito. Isso ocorreu devido ao fato de que, se vários mecanismos JavaScript implementarem a otimização de chamada de cauda descrita na especificação de idioma, a chamada de Function.caller poderá começar a produzir resultados incorretos. Exemplo de uso:


     const a = function() { console.log(a.caller == b); } const b = function() { a(); } b(); // true 

  • Function.toString - Retorna uma representação de string da função. Esse é um recurso muito poderoso que permite examinar o conteúdo de uma função e seus argumentos:


     const getFullName = (name, surname, middlename) => { console.log(`${surname} ${name} ${middlename}`); } getFullName.toString() /* * "(name, surname, middlename) => { * console.log(`${surname} ${name} ${middlename}`); * }" */ 

    Tendo recebido uma representação em string de uma função, podemos analisá-la e analisá-la. Isso pode ser usado, por exemplo, para retirar os nomes dos argumentos das funções e, dependendo do nome, substituir automaticamente o parâmetro desejado. Em geral, existem duas maneiras de analisar:


    • Analisando muitos regulares e obtemos um nível aceitável de confiabilidade (pode não funcionar se não cobrirmos todos os tipos possíveis de entradas de função).
    • Nós obtemos a representação de string da função e a colocamos no analisador JavaScript finalizado (por exemplo, esprima ou bolota ) e, em seguida, trabalhamos com o AST estruturado. Exemplo de análise AST via esprima. Também posso aconselhar um bom relatório sobre os analisadores de Alexei Okhrimenko.


Exemplos simples com análise de função regular:


Obtendo uma lista de argumentos de função
 /** *    . * @param fn  */ const getFunctionParams = fn => { const COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/gm; const DEFAULT_PARAMS = /=[^,]+/gm; const FAT_ARROW = /=>.*$/gm; const ARGUMENT_NAMES = /([^\s,]+)/g; const formattedFn = fn .toString() .replace(COMMENTS, "") .replace(FAT_ARROW, "") .replace(DEFAULT_PARAMS, ""); const params = formattedFn .slice(formattedFn.indexOf("(") + 1, formattedFn.indexOf(")")) .match(ARGUMENT_NAMES); return params || []; }; const getFullName = (name, surname, middlename) => { console.log(surname + ' ' + name + ' ' + middlename); }; console.log(getFunctionParams(getFullName)); // ["name", "surname", "middlename"] 


Obtendo a função corporal
 /** *     . * @param fn  */ const getFunctionBody = fn => { const restoreIndent = body => { const lines = body.split("\n"); const bodyLine = lines.find(line => line.trim() !== ""); let indent = typeof bodyLine !== "undefined" ? (/[ \t]*/.exec(bodyLine) || [])[0] : ""; indent = indent || ""; return lines.map(line => line.replace(indent, "")).join("\n"); }; const fnStr = fn.toString(); const rawBody = fnStr.substring( fnStr.indexOf("{") + 1, fnStr.lastIndexOf("}") ); const indentedBody = restoreIndent(rawBody); const trimmedBody = indentedBody.replace(/^\s+|\s+$/g, ""); return trimmedBody; }; //       getFullName const getFullName = (name, surname, middlename) => { console.log(surname + ' ' + name + ' ' + middlename); }; console.log(getFunctionBody(getFullName)); 


É importante observar que, ao usar o minificador, o próprio código dentro da função analisada e seus argumentos podem ser otimizados e, portanto, alterados.


3. Trabalhar com objetos


O JavaScript tem um objeto Object global que contém muitos métodos para trabalhar dinamicamente com objetos.


A maioria desses métodos já existe há muito tempo no idioma e é amplamente utilizada.


Propriedades do objeto


  • Object.assign - para copiar convenientemente as propriedades de um ou mais objetos para o objeto especificado pelo primeiro parâmetro:


     Object.assign({}, { a: 1 }, { b: 2 }, { c: 3 }) // {a: 1, b: 2, c: 3} 

  • Object.keys e Object.values - retorna uma lista de chaves ou uma lista de valores do objeto:


     const obj = { a: 1, b: 2, c: 3 }; console.log(Object.keys(obj)); // ["a", "b", "c"] console.log(Object.values(obj)); // [1, 2, 3] 

  • Object.entries - retorna uma lista de suas propriedades no formato [[chave1, valor1], [chave2, valor2]] :


     const obj = { a: 1, b: 2, c: 3 }; console.log(Object.entries(obj)); // [["a", 1], ["b", 2], ["c", 3]] 

  • Object.prototype.hasOwnProperty - Verifica se uma propriedade está contida em um objeto (não em sua cadeia de protótipos):


     const obj = { a: 1 }; obj.__proto__ = { b: 2 }; console.log(obj.hasOwnProperty('a')); // true console.log(obj.hasOwnProperty('b')) // false 

  • Object.getOwnPropertyNames - retorna uma lista de suas próprias propriedades, incluindo enumeradas e não enumeradas:


     const obj = { a: 1, b: 2 }; Object.defineProperty(obj, 'c', { value: 3, enumerable: false }); //    for (let key in obj) { console.log(key); } // "a", "b" console.log(Object.getOwnPropertyNames(obj)); // [ "a", "b", "c" ] 

  • Object.getOwnPropertySymbols - retorna uma lista própria (contida no objeto e não em sua cadeia de protótipos):


     const obj = {}; const a = Symbol('a'); obj[a] = 1; console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(a) ] 

  • Object.prototype.propertyIsEnumerable - verifica se uma propriedade é enumerável (por exemplo, está disponível nos loops for-in e for-of):


     const arr = [ ' ' ]; console.log(arr.propertyIsEnumerable(0)); // true —  ' '   console.log(arr.propertyIsEnumerable('length')); // false —  length    


Descritores de Propriedade de Objeto


Os descritores permitem ajustar os parâmetros de propriedade. Usando-os, podemos convenientemente criar nossos próprios interceptores enquanto lê / escreve qualquer propriedade (getters e setters - get / set), tornar propriedades imutáveis ​​ou não enumeráveis, e várias outras coisas.


  • Object.defineProperty e Object.defineProperties - cria um ou mais descritores de propriedades. Crie seu próprio descritor com getter e setter:


     const obj = { name: '', surname: '' }; Object.defineProperty(obj, 'fullname', { //     fullname get: function() { return `${this.name} ${this.surname}`; }, //     fullname (     delete obj.fullname) set: function(value) { const [name, surname] = value.split(' '); this.name = name; this.surname = surname; }, }); console.log(obj.fullname); //   obj.fullname = ' '; console.log(obj.name); //  console.log(obj.surname); //  

    No exemplo acima, a propriedade fullname não tinha seu próprio valor, mas trabalhou dinamicamente com as propriedades de nome e sobrenome. Não é necessário definir um getter e um setter ao mesmo tempo - podemos deixar apenas o getter e obter uma propriedade somente leitura. Ou podemos adicionar uma ação adicional no setter, além de definir o valor, por exemplo, registro.
    Além das propriedades get / set, os descritores têm várias outras propriedades para configurar:


     const obj = {}; //      get/set,       value.    get/set  value.   — undefined. Object.defineProperty(obj, 'name', { value: '' }); // ,         (for-in, for-of, Object.keys).   — false. Object.defineProperty(obj, 'a', { enumerable: true }); //         defineProperty     delete.   — false. Object.defineProperty(obj, 'b', { configurable: false }); //      .   — false. Object.defineProperty(obj, 'c', { writable: true }); 

  • Object.getOwnPropertyDescriptor e Object.getOwnPropertyDescriptors - permitem obter o descritor de objeto desejado ou sua lista completa:


     const obj = { a: 1, b: 2 }; console.log(Object.getOwnPropertyDescriptor(obj, "a")); // { configurable: true, enumerable: true, value: 1, writable: true } /** * { * a: { configurable: true, enumerable: true, value: 1, writable: true }, * b: { configurable: true, enumerable: true, value: 2, writable: true } * } */ console.log(Object.getOwnPropertyDescriptors(obj)); 


Criando restrições ao trabalhar com objetos


  • Object.freeze - "congela" as propriedades de um objeto. A conseqüência desse "congelamento" é a imutabilidade completa das propriedades do objeto - elas não podem ser alteradas e excluídas, adicionadas novas, alterados os descritores:


     const obj = Object.freeze({ a: 1 }); //       ,       . obj.a = 2; obj.b = 3; console.log(obj); // { a: 1 } console.log(Object.isFrozen(obj)) // true 

  • Object.seal - "sela" as propriedades de um objeto. A vedação é semelhante ao Object.freeze, mas possui várias diferenças. Nós, como no Object.freeze, proibimos adicionar novas propriedades, excluir as existentes, alterar seus descritores, mas, ao mesmo tempo, podemos alterar os valores das propriedades:


     const obj = Object.seal({ a: 1 }); obj.a = 2; //  a   2 //     ,       . obj.b = 3; console.log(obj); // { a: 2 } console.log(Object.isSealed(obj)) // true 

  • Object.preventExtensions - proíbe adicionar novas propriedades / descritores:


     const obj = Object.preventExtensions({ a: 1 }); obj.a = 2; //       ,       . obj.b = 3; console.log(obj); // { a: 2 } console.log(Object.isExtensible(obj)) // false 


Protótipos de Objetos


  • Object.create - para criar um objeto com o protótipo especificado no parâmetro Esse recurso pode ser usado tanto para a herança do protótipo quanto para a criação de objetos "limpos", sem propriedades do Object.prototype :


     const pureObj = Object.create(null); 

  • Object.getPrototypeOf e Object.setPrototypeOf - para obter / alterar o protótipo de um objeto:


     const duck = {}; const bird = {}; Object.setPrototypeOf(duck, bird); console.log(Object.getPrototypeOf(duck) === bird); // true console.log(duck.__proto__ === bird); // true 

  • Object.prototype.isPrototypeOf - Verifica se o objeto atual está contido na cadeia de protótipos de outro:


     const duck = {}; const bird = {}; duck.__proto__ = bird; console.log(bird.isPrototypeOf(duck)); // true 


4. API do Reflect


Com o advento do ES6, um objeto Reflect global foi adicionado ao JavaScript para armazenar vários métodos relacionados à reflexão e introspecção.


A maioria de seus métodos é o resultado da transferência de métodos existentes de objetos globais como Object e Function para um espaço para nome separado, com um pouco de refatoração para um uso mais confortável.


A transferência de funções para o objeto Reflect não apenas facilitou a busca dos métodos necessários para reflexão e deu maior semântica, mas também evitou situações desagradáveis ​​quando nosso objeto não contém Object.prototype em seu protótipo, mas queremos usar os métodos a partir daí:


 let obj = Object.create(null); obj.qwerty = 'qwerty'; console.log(obj.__proto__) // null console.log(obj.hasOwnProperty('qwerty')) // Uncaught TypeError: obj.hasOwnProperty is not a function console.log(obj.hasOwnProperty === undefined); // true console.log(Object.prototype.hasOwnProperty.call(obj, 'qwerty')); // true 

A refatoração tornou o comportamento dos métodos mais explícito e monótono. Por exemplo, se anteriormente, ao chamar Object.defineProperty em um valor incorreto (como um número ou uma string), uma exceção era lançada, mas, ao mesmo tempo, chamava Object.getOwnPropertyDescriptor em um descritor de objeto inexistente retornado silenciosamente indefinido, então métodos semelhantes do Reflect sempre lançavam exceções por dados incorretos .


Vários novos métodos também foram adicionados:


  • Reflect.construct é uma alternativa mais conveniente para Object.create , que permite não apenas criar um objeto com o protótipo especificado, mas também inicializá-lo imediatamente:


     function Person(name, surname) { this.name = this.formatParam(name); this.surname = this.formatParam(surname); } Person.prototype.formatParam = function(param) { return param.slice(0, 1).toUpperCase() + param.slice(1).toLowerCase(); } const oldPerson = Object.create(Person.prototype); // {} Person.call(oldPerson, '', ''); // {name: "", surname: ""} const newPerson = Reflect.construct(Person, ['', '']); // {name: "", surname: ""} 

  • Reflect.ownKeys - retorna uma matriz de propriedades pertencentes ao objeto especificado (e não aos objetos na cadeia de protótipos):


     let person = { name: '', surname: '' }; person.__proto__ = { age: 30 }; console.log(Reflect.ownKeys(person)); // ["name", "surname"] 

  • Reflect.deleteProperty - uma alternativa ao operador de exclusão , feito na forma de um método:


     let person = { name: '', surname: '' }; delete person.name; // person = {surname: ""} Reflect.deleteProperty(person, 'surname'); // person = {} 

  • Reflect.has - uma alternativa ao operador in , feito na forma de um método:


     let person = { name: '', surname: '' }; console.log('name' in person); // true console.log(Reflect.has(person, 'name')); // true 

  • Reflect.get e Reflect.set - para ler / alterar propriedades do objeto:


     let person = { name: '', surname: '' }; console.log(Reflect.get(person, 'name')); //  Reflect.set(person, 'surname', '') // person = {name: "", surname: ""} 


Mais detalhes sobre as alterações podem ser encontrados aqui .


Refletir metadados


Além dos métodos de objeto Reflect listados acima, existe uma proposta experimental para vincular convenientemente vários metadados a objetos.


Os metadados podem ser qualquer informação útil que não esteja diretamente relacionada ao objeto, por exemplo:


  • O TypeScript, quando o sinalizador emitDecoratorMetadata está ativado, grava informações sobre tipos nos metadados, permitindo que você os acesse em tempo de execução. Além disso, essas informações podem ser obtidas pelo design da chave: type:
     const typeData = Reflect.getMetadata("design:type", object, propertyName); 
  • A popular biblioteca InversifyJS para controle de inversão armazena várias informações sobre os relacionamentos descritos nos metadados.

No momento, esse polyfill é usado para trabalhar em navegadores .


5. Símbolos


Os símbolos são um novo tipo de dados imutáveis, usado principalmente para criar nomes exclusivos para identificadores de propriedade do objeto. Temos a capacidade de criar personagens de duas maneiras:


  1. Símbolos locais - o texto nos parâmetros da função Symbol não afeta a exclusividade e é necessário apenas para depuração:


     const sym1 = Symbol('name'); const sym2 = Symbol('name'); console.log(sym1 == sym2); // false 

  2. Caracteres globais - os caracteres são armazenados no registro global, portanto, os caracteres com a mesma chave são iguais:


     const sym3 = Symbol.for('name'); const sym4 = Symbol.for('name'); const sym5 = Symbol.for('other name'); console.log(sym3 == sym4); // true,        'name' console.log(sym3 == sym5); // false,     


A capacidade de criar esses identificadores nos permite não ter medo de substituir alguma propriedade em um objeto desconhecido para nós. Essa qualidade permite que os criadores do padrão adicionem facilmente novas propriedades padrão aos objetos, sem quebrar a compatibilidade com várias bibliotecas existentes (que já podem definir a mesma propriedade) e o código do usuário. Portanto, existem vários símbolos padrão e alguns deles oferecem novas oportunidades de reflexão:


  • Symbol.iterator - permite criar suas próprias regras para iterar objetos usando o operador for-of ou ... spread :


     let arr = [1, 2, 3]; //       arr[Symbol.iterator] = function() { const self = this; let pos = this.length - 1; return { next() { if (pos >= 0) { return { done: false, value: self[pos--] }; } else { return { done: true }; } } } }; console.log([...arr]); // [3, 2, 1] 

  • Symbol.hasInstance é um método que determina se um construtor reconhece um objeto como sua instância. Usado pelo operador instanceof:


     class MyArray { static [Symbol.hasInstance](instance) { return Array.isArray(instance); } } console.log([] instanceof MyArray); // true 

  • Symbol.isConcatSpreadable - Indica se a matriz deve achatar quando concatenada em Array.concat:


     let firstArr = [1, 2, 3]; let secondArr = [4, 5, 6]; firstArr.concat(secondArr); // [1, 2, 3, 4, 5, 6] secondArr[Symbol.isConcatSpreadable] = false; console.log(firstArr.concat(secondArr)); // [1, 2, 3, [4, 5, 6]] 

  • Symbol.species - permite especificar qual construtor será usado para criar objetos derivados dentro da classe.
    Por exemplo, temos uma classe Array padrão para trabalhar com matrizes e ele possui um método .map que cria uma nova matriz com base na atual. Para descobrir qual classe usar para criar essa nova matriz, Array chama this.constructor [Symbol.species] assim:


     Array.prototype.map = function(cb) { const ArrayClass = this.constructor[Symbol.species]; const result = new ArrayClass(this.length); this.forEach((value, index, arr) => { result[index] = cb(value, index, arr); }); return result; } 

    Assim, substituindo Symbol.species, podemos criar nossa própria classe para trabalhar com matrizes e dizer que todos os métodos padrão como .map, .reduce etc. não retornam uma instância da classe Array, mas uma instância da nossa classe:


     class MyArray extends Array { static get [Symbol.species]() { return this; } } const arr = new MyArray(1, 2, 3); // [1, 2, 3] console.log(arr instanceof MyArray); // true console.log(arr instanceof Array); // true //   Array.map     Array,    Symbol.species  this      MyArray const doubledArr = arr.map(x => x * 2); console.log(doubledArr instanceof MyArray); // true console.log(doubledArr instanceof Array); // true 

    Obviamente, isso funciona não apenas com matrizes, mas também com outras classes padrão. Além disso, mesmo se simplesmente criarmos nossa própria classe com métodos que retornam novas instâncias da mesma classe, devemos usar this.constructor [Symbol.species] para obter uma referência ao construtor.


  • Symbol.toPrimitive - permite especificar como converter nosso objeto em um valor primitivo. Se antes, para reduzir a um primitivo, precisávamos usar toString junto com valueOf, agora tudo pode ser feito em um método conveniente:


     const figure = { id: 1, name: '', [Symbol.toPrimitive](hint) { if (hint === 'string') { return this.name; } else if (hint === 'number') { return this.id; } else { // default return this.name; } } } console.log(`${figure}`); // hint = string console.log(+figure); // hint = number console.log(figure + ''); // hint = default 

  • Symbol.match - permite criar suas próprias classes de manipulador para o método da função String.prototype.match :


     class StartAndEndsWithMatcher { constructor(value) { this.value = value; } [Symbol.match](str) { const startsWith = str.startsWith(this.value); const endsWith = str.endsWith(this.value); if (startsWith && endsWith) { return [this.value]; } return null; } } const testMatchResult = '||'.match(new StartAndEndsWithMatcher('|')); console.log(testMatchResult); // ["|"] const catMatchResult = '|'.match(new StartAndEndsWithMatcher('|')); console.log(catMatchResult) // null 

    Symbol.replace , Symbol.search Symbol.split String.prototype .



, ( reflect-metadata ) . - , , . :


 const validationRules = Symbol('validationRules'); const person = { name: '', surname: '' }; person[validationRules] = { name: ['max-length-256', 'required'], surname: ['max-length-256'] }; 

6. (Proxy)


Proxy , Reflect API Symbols ES6, // , , . , .


, data-binding MobX React, Vue . .


:


 const formData = { login: 'User', password: 'pass' }; const proxyFormData = new Proxy(formData, { set(target, name, value) { target[name] = value; this.forceUpdate(); //   React- } }); //       forceUpdate()    React proxyFormData.login = 'User2'; //     ,   -      proxyFormData.age = 20; 

, /:


 const formData = { login: 'User', password: 'pass' }; const proxyFormData = {}; for (let param in formData) { Reflect.defineProperty(proxyFormData, `__private__${param}`, { value: formData[param], enumerable: false, configurable: true }); Reflect.defineProperty(proxyFormData, param, { get: function() { return this[`__private__${param}`]; }, set: function(value) { this[`__private__${param}`] = value; this.forceUpdate(); //   React- }, enumerable: true, configurable: true }); } //       forceUpdate()    React proxyFormData.login = 'User2'; //                  -  Reflect.defineProperty proxyFormData.age = 20; 

-, — Proxy ( , ), / , delete obj[name] .


7.


JavaScript , ECMAScript 4, . , .


You Don't Know JS .

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


All Articles