Metaprogramación de JavaScript

La metaprogramación es un tipo de programación asociada con la creación de programas que generan otros programas como resultado de su trabajo, o programas que cambian a sí mismos durante la ejecución. (Wikipedia)

En un lenguaje más simple, la metaprogramación dentro de JavaScript puede considerarse mecanismos que le permiten analizar y cambiar el programa en tiempo real dependiendo de cualquier acción. Y, muy probablemente, de alguna manera los usas al escribir scripts todos los días.


JavaScript, por su naturaleza, es un lenguaje dinámico muy poderoso y le permite escribir código flexible muy bien:


/** *   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 

Un código similar para crear métodos dinámicamente en otros idiomas muy a menudo puede requerir una sintaxis especial o API para esto. Por ejemplo, PHP también es un lenguaje dinámico, pero en él requerirá más esfuerzo:


 <?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 

Además de la sintaxis flexible, también tenemos un montón de funciones útiles para escribir código dinámico: Object.create, Object.defineProperty, Function.apply y muchos otros.


Considérelos con más detalle.


  1. Generación de código
  2. Trabajar con funciones
  3. Trabajar con objetos
  4. API de reflejo
  5. Los símbolos
  6. Proxy
  7. Conclusión

1. Generación de código


La herramienta estándar para ejecutar código dinámicamente es la función eval , que le permite ejecutar código desde la cadena pasada:


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

Desafortunadamente, eval tiene muchos matices:


  • Si nuestro código está escrito en modo estricto ('use estricto'), las variables declaradas dentro de eval no serán visibles en el código de evaluación de llamada. Al mismo tiempo, el código en sí dentro de eval siempre puede cambiar las variables externas.
  • el código dentro de eval puede ejecutarse tanto en el contexto global (si se llama a través de window.eval) como en el contexto de la función dentro de la cual se realizó la llamada (si solo eval, sin ventana).
  • Pueden surgir problemas debido a la minificación JS, cuando los nombres de las variables se reemplazan por otros más cortos para reducir el tamaño. El código pasado como una cadena para evaluar generalmente no toca el minificador, debido a esto podemos comenzar a acceder a variables externas usando nombres antiguos no minificados, lo que conducirá a errores sutiles.

Hay una gran alternativa para resolver estos problemas: la nueva función .


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

A diferencia de eval, siempre podemos pasar parámetros explícitamente a través de los argumentos de una función y darle dinámicamente el contexto de esta (a través de Function.apply o Function.call ). Además, la función creada siempre se llama en el ámbito global.


En los viejos tiempos, eval se usaba a menudo para cambiar dinámicamente el código, ya que JavaScript tenía muy pocos mecanismos de reflexión y era imposible hacerlo sin eval. Pero en el estándar de lenguaje moderno, ha aparecido mucha más funcionalidad de alto nivel y eval se usa ahora con mucha menos frecuencia.


2. Trabajar con funciones


JavaScript nos proporciona muchas herramientas excelentes para trabajar dinámicamente con funciones, permitiéndonos obtener información diversa sobre la función en tiempo de ejecución y cambiarla:


  • Function.length : le permite averiguar la cantidad de argumentos de una función:


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

  • Function.apply y Function.call : le permiten cambiar dinámicamente el contexto de esta función:


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

    Se diferencian entre sí solo en que, en Function.apply, los argumentos de la función se sirven como una matriz, y en Function.call, separados por comas. Esta característica a menudo se usaba antes para pasar una lista de argumentos a la función como una matriz. Un ejemplo común es la función Math.max (por defecto, no puede funcionar con matrices):


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

    Con el advenimiento del nuevo operador de propagación, simplemente puede escribir esto:


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

  • Function.bind : le permite crear una copia de una función a partir de una existente, pero con un contexto diferente:


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

  • Function.caller : le permite obtener la función de llamada. No se recomienda usarlo , ya que está ausente en el estándar de idioma y no funcionará en modo estricto. Esto se debió al hecho de que si varios motores de JavaScript implementan la optimización de llamada de cola descrita en la especificación del lenguaje, entonces llamar a Function.caller puede comenzar a producir resultados incorrectos. Ejemplo de uso:


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

  • Function.toString : devuelve una representación de cadena de la función. Esta es una característica muy poderosa que le permite examinar tanto el contenido de una función como sus argumentos:


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

    Habiendo recibido una representación de cadena de una función, podemos analizarla y analizarla. Esto se puede utilizar para, por ejemplo, extraer los nombres de los argumentos de la función y, según el nombre, sustituir automáticamente el parámetro deseado. En general, hay dos formas de analizar:




Ejemplos simples con análisis de funciones regulares:


Obtener una lista de argumentos de funciones
 /** *    . * @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"] 


Obtención de la función 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)); 


Es importante tener en cuenta que cuando se usa el minificador, tanto el código en sí mismo dentro de la función analizada como sus argumentos pueden optimizarse y, por lo tanto, cambiar.


3. Trabajar con objetos.


JavaScript tiene un objeto Object global que contiene muchos métodos para trabajar dinámicamente con objetos.


La mayoría de estos métodos a partir de ahí han existido durante mucho tiempo en el lenguaje y son ampliamente utilizados.


Propiedades del objeto


  • Object.assign : para copiar convenientemente las propiedades de uno o más objetos al objeto especificado por el primer parámetro:


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

  • Object.keys y Object.values : devuelve una lista de claves o una lista de valores de 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 : devuelve una lista de sus propiedades en el formato [[clave1, valor1], [clave2, valor2]] :


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

  • Object.prototype.hasOwnProperty : comprueba si una propiedad está contenida en un objeto (no en su cadena de prototipo):


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

  • Object.getOwnPropertyNames : devuelve una lista de sus propias propiedades, incluidas las enumeradas y las no 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 : devuelve una lista de sus propios caracteres (contenidos en el objeto y no en su cadena de prototipo):


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

  • Object.prototype.propertyIsEnumerable : comprueba si una propiedad es enumerable (por ejemplo, está disponible en bucles for-in, for-of):


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


Descriptores de propiedad de objeto


Los descriptores le permiten ajustar los parámetros de propiedad. Al usarlos, podemos hacer convenientemente nuestros propios interceptores mientras leemos / escribimos cualquier propiedad (getters y setters - get / set), hacemos que las propiedades sean inmutables o no enumerables, y una serie de otras cosas.


  • Object.defineProperty y Object.defineProperties : crea uno o más descriptores de propiedades. Crea tu propio descriptor con getter y 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); //  

    En el ejemplo anterior, la propiedad de nombre completo no tenía su propio valor, sino que funcionaba dinámicamente con las propiedades de nombre y apellido. No es necesario definir tanto un getter como un setter al mismo tiempo; solo podemos dejar el getter y obtener una propiedad de solo lectura. O podemos agregar una acción adicional en el setter junto con la configuración del valor, por ejemplo, el registro.
    Además de las propiedades get / set, los descriptores tienen varias propiedades más 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 y Object.getOwnPropertyDescriptors : le permiten obtener el descriptor de objeto deseado o su 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)); 


Crear restricciones al trabajar con objetos


  • Object.freeze - "congela" las propiedades de un objeto. La consecuencia de tal "congelación" es la inmutabilidad completa de las propiedades del objeto: no se pueden cambiar ni eliminar, se agregan otras nuevas, se cambian los descriptores:


     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 - "sella" las propiedades de un objeto. El sellado es similar a Object.freeze, pero tiene varias diferencias. Nosotros, como en Object.freeze, prohibimos agregar nuevas propiedades, eliminar las existentes, cambiar sus descriptores, pero al mismo tiempo podemos cambiar los valores de las propiedades:


     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 : prohíbe agregar nuevas propiedades / descriptores:


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


Prototipos de objetos


  • Object.create : para crear un objeto con el prototipo especificado en el parámetro. Esta característica se puede usar tanto para la herencia de prototipos como para crear objetos "limpios", sin propiedades de Object.prototype :


     const pureObj = Object.create(null); 

  • Object.getPrototypeOf y Object.setPrototypeOf - para obtener / cambiar el prototipo de un 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 : comprueba si el objeto actual está contenido en la cadena de prototipo de otro:


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


4. Reflejar API


Con la llegada de ES6, se ha agregado un objeto Reflect global a JavaScript para almacenar varios métodos relacionados con la reflexión y la introspección.


La mayoría de sus métodos son el resultado de transferir métodos existentes de objetos globales como Objeto y Función a un espacio de nombres separado con una pequeña refactorización para un uso más cómodo.


La transferencia de funciones al objeto Reflect no solo facilitó la búsqueda de los métodos necesarios para la reflexión y proporcionó una mayor semántica, sino que también evitó situaciones desagradables cuando nuestro objeto no contiene Object.prototype en su prototipo, sino que queremos utilizar los métodos a partir de ahí:


 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 

La refactorización ha hecho que el comportamiento de los métodos sea más explícito y monótono. Por ejemplo, si antes, al llamar a Object.defineProperty con un valor incorrecto (como un número o una cadena), se generaba una excepción, pero al mismo tiempo, llamar a Object.getOwnPropertyDescriptor en un descriptor de objeto inexistente devuelto en silencio sin definir, luego métodos similares de Reflect siempre arrojan excepciones para datos incorrectos .


También se han agregado varios métodos nuevos:


  • Reflect.construct es una alternativa más conveniente a Object.create , que permite no solo crear un objeto con el prototipo especificado, sino también inicializarlo de inmediato:


     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 : devuelve una matriz de propiedades que pertenecen al objeto especificado (y no a los objetos en la cadena del prototipo):


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

  • Reflect.deleteProperty : una alternativa al operador de eliminación , realizada en forma de método:


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

  • Reflect.has : una alternativa al operador in , realizada en forma de método:


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

  • Reflect.get y Reflect.set : para leer / cambiar las propiedades del objeto:


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


Más detalles sobre los cambios se pueden encontrar aquí .


Reflejar metadatos


Además de los métodos de objeto Reflect enumerados anteriormente, existe una propuesta experimental para vincular convenientemente varios metadatos a los objetos.


Los metadatos pueden ser cualquier información útil que no esté directamente relacionada con el objeto, por ejemplo:


  • TypeScript, cuando el indicador emitDecoratorMetadata está activado, escribe información sobre los tipos en los metadatos, lo que le permite acceder a ellos en tiempo de ejecución. Además, esta información se puede obtener mediante el diseño clave: tipo:
     const typeData = Reflect.getMetadata("design:type", object, propertyName); 
  • La popular biblioteca InversifyJS para control de inversión almacena diversa información sobre las relaciones descritas en los metadatos.

Por el momento, este polyfill se utiliza para trabajar en navegadores .


5. Símbolos


Los símbolos son un nuevo tipo de datos inmutable, utilizado principalmente para crear nombres únicos para identificadores de propiedades de objeto. Tenemos la capacidad de crear personajes de dos maneras:


  1. Símbolos locales: el texto en los parámetros de la función Símbolo no afecta la unicidad y solo es necesario para la depuración:


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

  2. Caracteres globales: los caracteres se almacenan en el registro global, por lo que los caracteres con la misma clave son iguales:


     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,     


La capacidad de crear tales identificadores nos permite no tener miedo de que podamos sobrescribir alguna propiedad en un objeto desconocido para nosotros. Esta calidad permite a los creadores del estándar agregar fácilmente nuevas propiedades estándar a los objetos, sin romper la compatibilidad con varias bibliotecas existentes (que ya podrían definir la misma propiedad) y el código de usuario. Por lo tanto, hay varios símbolos estándar y algunos de ellos brindan nuevas oportunidades para la reflexión:


  • Symbol.iterator : le permite crear sus propias reglas para iterar objetos utilizando el operador for-of o ... 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 es un método que determina si un constructor reconoce un objeto como su instancia. Utilizado por el operador instanceof:


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

  • Symbol.isConcatSpreadable : indica si la matriz debe aplanarse cuando se concatena en 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 : le permite especificar qué constructor se usará para crear objetos derivados dentro de la clase.
    Por ejemplo, tenemos una clase de matriz estándar para trabajar con matrices y tiene un método .map que crea una nueva matriz basada en la actual. Para averiguar qué clase usar para crear esta nueva matriz, Array llama a this.constructor [Symbol.species] de esta manera:


     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; } 

    Por lo tanto, anulando Symbol.species, podemos crear nuestra propia clase para trabajar con matrices y decir que todos los métodos estándar como .map, .reduce, etc. no devuelven una instancia de la clase Array, sino una instancia de nuestra clase:


     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 

    Por supuesto, esto funciona no solo con matrices, sino también con otras clases estándar. Además, incluso si simplemente creamos nuestra propia clase con métodos que devuelven nuevas instancias de la misma clase, deberíamos usar this.constructor [Symbol.species] para obtener una referencia al constructor.


  • Symbol.toPrimitive : le permite especificar cómo convertir nuestro objeto a un valor primitivo. Si antes, para reducir a una primitiva, necesitábamos usar toString junto con valueOf, ahora todo se puede hacer en un 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 : le permite crear sus propias clases de manejador para el método de la función 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/es417097/


All Articles