Conozca la metaprogramación en JavaScript

Este artículo es compartido por Ye Yiyi de la comunidad en la nube de Huawei " Metaprogramación para hacer que el código sea más descriptivo, expresivo y flexible ".

fondo

En la segunda mitad del año pasado, agregué muchos libros técnicos en varias categorías a mi estantería de WeChat y leí algunos de ellos de forma intermitente.

Leer sin un plan dará pocos resultados.

Cuando comienza el nuevo año, estoy listo para intentar algo más, como una semana de lectura. Reserva de 1 a 2 semanas no consecutivas cada mes para leer un libro en su totalidad.

Aunque este "cómo jugar" es común y rígido, es efectivo. Llevo tres meses leyéndolo.

Hay dos planes de lectura para abril y la serie "JavaScript que no conoces" ha llegado a su fin.

 

Ya leí libros : "El camino hacia una arquitectura simple", "Node.js de una manera sencilla y fácil", "JavaScript que no conoces (Volumen 1)", "JavaScript que no conoces (Volumen 2)".

 

 

Libro de la semana de lectura actual : "JavaScript que no conoces (volumen 2)".

 

metaprogramación

nombre de la función

Hay muchas formas de expresar una función en un programa y no siempre está claro cuál debería ser el "nombre" de la función.

Más importante aún, debemos determinar si el "nombre" de la función es simplemente su atributo de nombre (sí, las funciones tienen un atributo llamado nombre), o si apunta a su nombre vinculado léxicamente, como barra de función(){. en .}.

El atributo de nombre se utiliza con fines de metaprogramación.

De forma predeterminada, el nombre léxico de la función (si corresponde) también se establece en su atributo de nombre. De hecho, la especificación ES5 (y anteriores) no requiere formalmente este comportamiento. La configuración del atributo de nombre no es estándar, pero sigue siendo relativamente confiable. Esto se ha estandarizado en ES6.

En ES6, ahora existe un conjunto de reglas de derivación que pueden asignar razonablemente un valor al atributo de nombre de una función, incluso si la función no tiene un nombre léxico disponible.

Por ejemplo:

var abc = función () {
  // ..
};

abc.nombre; // "a B C"

Aquí hay algunas otras formas de derivación de nombres (o falta de ella) en ES6:

(función(){ .. }); // nombre:
(función*(){ .. }); // nombre:
ventana.foo = función(){ .. }; // nombre:
clase Impresionante {
    constructor() { .. } // nombre: Impresionante
    gracioso() { .. } // nombre: gracioso
}

var c = clase Impresionante { .. }; // nombre: Impresionante
var o = {
    foo() { .. }, // nombre: foo
    *bar() { .. }, // nombre: barra
    baz: () => { .. }, // nombre: baz
    bam: función(){ .. }, // nombre: bam
    obtener qué() { .. }, // nombre: obtener qué
    establecer fuz() { .. }, // nombre: establecer fuz
    ["b" + "iz"]:
      función(){ .. }, // nombre: negocio
    [Símbolo ("buz")]:
      función(){ .. } // nombre: [buz]
};

var x = o.foo.bind(o); // nombre: foo enlazado
(función(){ .. }).bind( o ); // nombre: enlazado
exportar función predeterminada() { .. } // nombre: predeterminado
var y = nueva función(); // nombre: anónimo
donde Función Generador =
    función*(){}. proto.constructor;
var z = nueva Función Generador(); // nombre: anónimo

De forma predeterminada, la propiedad del nombre no se puede escribir, pero sí se puede configurar, lo que significa que se puede modificar manualmente usando Object.defineProperty(..) si es necesario.

metaatributo

Los metaatributos proporcionan metainformación especial en forma de acceso a atributos que no se puede obtener mediante otros métodos.

Tomando new.target como ejemplo, la palabra clave new se utiliza como contexto para el acceso al atributo. Obviamente, lo nuevo en sí no es un objeto, por lo que esta función es muy especial. Cuando new.target se usa dentro de una llamada de constructor (una función/método activado por new), new se convierte en un contexto virtual, lo que permite que new.target apunte al constructor de destino que llama a new.

Este es un claro ejemplo de una operación de metaprogramación, porque su propósito es determinar desde dentro de la llamada al constructor cuál era el nuevo objetivo original, generalmente hablando para introspección (verificar tipo/estructura) o acceso a propiedades estáticas.

Por ejemplo, es posible que desees realizar diferentes acciones dentro de un constructor dependiendo de si se llama directamente o mediante una subclase:

clase padre {
  constructor() {
    if (nuevo.objetivo === Padre) {
      console.log('Pariente instanciado');
    } demás {
      console.log('Un niño instanciado');
    }
  }
}

clase Niño extiende Padre {}

var a = nuevo padre();
// Padre instanciado

var b = nuevo Niño();
// Un niño instanciado

Al constructor() dentro de la definición de la clase principal se le asigna en realidad el nombre léxico de la clase (Padre), aunque la sintaxis implica que la clase es una entidad separada del constructor.

símbolo público

JavaScript predefine algunos símbolos integrados llamados símbolos públicos (Símbolo conocido, WKS).

Estos símbolos se definen principalmente para proporcionar metapropiedades especializadas, de modo que estas metapropiedades puedan exponerse a programas JavaScript para obtener más control sobre el comportamiento de JavaScript.

Iterador de símbolo

Symbol.iterator representa una ubicación especial (atributo) en cualquier objeto. El mecanismo del lenguaje encuentra automáticamente un método en esta ubicación. Este método construye un iterador para consumir el valor de este objeto. Muchas definiciones de objetos tienen un valor predeterminado para este símbolo.

Sin embargo, también puede definir su propia lógica de iterador para valores de objetos arbitrarios definiendo la propiedad Symbol.iterator, incluso si esto anula el iterador predeterminado. El aspecto de metaprogramación aquí es que definimos un atributo de comportamiento que puede ser utilizado por otras partes de JavaScript (es decir, operadores y construcciones de bucle) cuando se trata del objeto definido.

Por ejemplo:

var cicatriz = [4, 5, 6, 7, 8, 9];

para (var v de arr) {
  consola.log(v);
}
// 4 5 6 7 8 9

// Definir un iterador que solo produzca valores en valores de índice impares
arr[Símbolo.iterador] = función* () {
  donde idx = 1;
  hacer {
    producir esto[idx];
  } mientras ((idx += 2) < this.length);
};

para (var v de arr) {
  consola.log(v);
}
// 5 7 9

Symbol.toStringTag y Symbol.hasInstance

Una de las tareas de metaprogramación más comunes es realizar una introspección de un valor para descubrir de qué tipo es, generalmente para determinar qué operaciones son apropiadas para realizar en él. Para los objetos, las técnicas de introspección más utilizadas son toString() e instanciade.

En ES6, puedes controlar el comportamiento de estas operaciones:

función Foo (saludo) {
  this.greeting = saludo;
}

Foo.prototype[Symbol.toStringTag] = 'Foo';

Objeto.defineProperty(Foo, Símbolo.hasInstance, {
  valor: función (inst) {
    return inst.saludo == 'hola';
  },
});

var a = nuevo Foo('hola'),
  b = nuevo Foo('mundo');

b[Symbol.toStringTag] = 'genial';

a.toString(); // [objeto Foo]
Cadena (b); // [objeto genial]
una instancia de Foo; // verdadero

b instancia de Foo; // FALSO

La notación @@toStringTag del prototipo (o de la instancia misma) especifica el valor de cadena utilizado cuando se cadena [objeto].

La notación @@hasInstance es un método en la función constructora que acepta un valor de objeto de instancia y devuelve verdadero o falso para indicar si el valor puede considerarse una instancia.

Símbolo.especie

Qué constructor usar (Array(..) o una subclase personalizada) al crear una subclase de Array y desea definir métodos heredados (como slice(..)). De forma predeterminada, llamar a slice(..) en una instancia de una subclase Array crea una nueva instancia de esta subclase.

Este requisito se puede metaprogramar anulando la definición @@especie predeterminada de una clase:

clase genial {
  // Diferir @@especies a subclases
  obtención estática [Símbolo.especie]() {
    devolver esto;
  }

  de nuevo() {
    devolver nuevo this.constructor[Symbol.species]();
  }
}

clase Diversión extiende Genial {}

clase Impresionante extiende Genial {
  //Forzar que se especifique @@species como constructor principal
  obtención estática [Símbolo.especie]() {
    volver Genial;
  }
}

var a = nueva Diversión(),
  b = nuevo Impresionante(),
  c = a.otra vez(),
  d = b.otra vez();

c instancia de Diversión; // verdadero
d instancia de Impresionante; // FALSO
d instancia de Cool; // verdadero

El comportamiento predeterminado de Symbol.species en constructores nativos integrados es devolver esto. No existe un valor predeterminado en la clase de usuario, pero como se muestra, esta característica de comportamiento es fácil de simular.

Si necesita definir un método para generar nuevas instancias, utilice la metaprogramación del patrón new this.constructor[Symbol.species](..) en lugar de codificar new this.constructor(..) o new XYZ(..). Las clases heredadas pueden luego personalizar Symbol.species para controlar qué constructor genera estas instancias.

interino

Una de las nuevas funciones de metaprogramación más obvias en ES6 es la función Proxy.

Un proxy es un objeto especial que usted crea y que "encapsula" otro objeto ordinario, o se encuentra frente a este objeto ordinario. Puede registrar una función de procesamiento especial (es decir, trampa) en el objeto proxy. Este programa se llamará al realizar varias operaciones en el proxy. Estos controladores tienen la oportunidad de realizar lógica adicional además de reenviar operaciones al objeto encapsulado/destino original.

Un ejemplo de una función de controlador de capturas que puede definir en un proxy es get, que intercepta la operación [[Get]] cuando intenta acceder a las propiedades de un objeto.

var objeto = { a: 1 },
  manejadores = {
    get(objetivo, clave, contexto) {
      // Nota: objetivo === obj,
      // contexto === pobj
      console.log('accediendo: ', clave);
      return Reflect.get(objetivo, clave, contexto);
    },
  },
  pobj = nuevo Proxy(obj, controladores);

obj.a;
// 1
pobj.a;
// accediendo a: a
// 1

Declaramos un método de denominación de función de procesamiento get(..) en los controladores (el segundo parámetro del objeto Proxy(..)), que acepta una referencia de objeto de destino (obj), un nombre de atributo clave ("a") y literales de cuerpo, y yo/receptor/agente (pobj).

Limitaciones de la agencia

A través de estas trampas de funciones de metaprogramación se puede manejar un amplio conjunto de operaciones básicas que se pueden realizar en objetos. Pero hay algunas operaciones que no pueden (al menos por ahora) interceptarse.

varobj = {a:1, b:2},
manejadores = { .. },
pobj = nuevo Proxy (obj, controladores);
tipo de objeto;
Cadena (objeto);

objeto + "";
obj == pobj;
obj === pobj

Resumir

Resumamos los contenidos principales de este artículo:

  • Antes de ES6, JavaScript ya tenía muchas funciones de metaprogramación y ES6 proporciona varias características nuevas que mejoran significativamente las capacidades de metaprogramación.
  • Desde la derivación de nombres de funciones para funciones anónimas hasta metapropiedades que brindan información sobre cómo se llama a un constructor, puede profundizar más que nunca en la estructura del tiempo de ejecución de su programa. Al exponer símbolos, puede anular funciones originales, como la conversión de tipos de objetos a tipos nativos. Los servidores proxy pueden interceptar y personalizar varias operaciones subyacentes de los objetos, y Reflect proporciona herramientas para simularlas.
  • El autor original recomienda: Primero, concéntrese en comprender cómo funciona el mecanismo central de este lenguaje. Y una vez que realmente comprenda cómo funciona JavaScript, es hora de comenzar a utilizar estas poderosas capacidades de metaprogramación para aplicar aún más el lenguaje.

Haga clic para seguir y conocer las nuevas tecnologías de Huawei Cloud lo antes posible ~

Linus tomó el asunto en sus propias manos para evitar que los desarrolladores del kernel reemplacen las pestañas con espacios. Su padre es uno de los pocos líderes que puede escribir código, su segundo hijo es el director del departamento de tecnología de código abierto y su hijo menor es un núcleo. Colaborador de código abierto Huawei: tomó 1 año convertir 5000 aplicaciones móviles de uso común Migración completa a Hongmeng Java es el lenguaje más propenso a vulnerabilidades de terceros Wang Chenglu, el padre de Hongmeng: el código abierto Hongmeng es la única innovación arquitectónica. En el campo del software básico en China, Ma Huateng y Zhou Hongyi se dan la mano para "eliminar rencores". Ex desarrollador de Microsoft: el rendimiento de Windows 11 es "ridículamente malo " " Aunque lo que Laoxiangji es de código abierto no es el código, las razones detrás de él. Son muy conmovedores. Meta Llama 3 se lanza oficialmente. Google anuncia una reestructuración a gran escala.
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4526289/blog/11054218
Recomendado
Clasificación