Conheça a metaprogramação em JavaScript

Este artigo foi compartilhado pela comunidade Huawei Cloud " Metaprogramação para tornar o código mais descritivo, expressivo e flexível " por Ye Yiyi.

fundo

No segundo semestre do ano passado, adicionei muitos livros técnicos à minha estante do WeChat em várias categorias e li alguns deles de forma intermitente.

Ler sem um plano produzirá poucos resultados.

Com o início do novo ano, estou pronto para tentar outra coisa, como uma semana de leitura. Reserve de 1 a 2 semanas não consecutivas por mês para ler um livro completo.

Embora este “como jogar” seja comum e rígido, é eficaz e estou lendo-o há três meses.

Há dois planos de leitura para abril, e a série “JavaScript You Don’t Know” chegou ao fim.

 

Já li livros : "O Caminho para a Arquitetura Simples", "Node.js de uma maneira simples e fácil", "JavaScript que você não conhece (Volume 1)", "JavaScript que você não conhece (Volume 2)".

 

 

Livro atual da Semana de Leitura : "JavaScript que você não conhece (Volume 2)".

 

metaprogramação

nome da função

Existem muitas maneiras de expressar uma função em um programa e nem sempre é claro qual deveria ser o “nome” da função.

Mais importante ainda, precisamos determinar se o “nome” da função é simplesmente seu atributo name (sim, as funções têm um atributo chamado name) ou se ele aponta para seu nome vinculado lexicalmente, como function bar(){. em .}.

O atributo name é usado para fins de metaprogramação.

Por padrão, o nome léxico da função (se houver) também é definido como seu atributo name. Na verdade, a especificação ES5 (e anteriores) não exige formalmente esse comportamento. A configuração do atributo name não é padrão, mas ainda é relativamente confiável. Isso foi padronizado no ES6.

No ES6, existe agora um conjunto de regras de derivação que podem atribuir razoavelmente um valor ao atributo name de uma função, mesmo que a função não tenha um nome léxico disponível.

por exemplo:

varabc=função(){
  // ..
};

abc.nome; //"abc"

Aqui estão algumas outras formas de derivação de nomes (ou falta dela) no ES6:

(função(){ .. }); // nome:
(função*(){ .. }); // nome:
janela.foo = function(){ .. }; // nome:
classe Incrível {
    construtor() { .. } // nome: Incrível
    engraçado() { .. } // nome: engraçado
}

var c = class Incrível { .. }; // nome: Incrível
var o = {
    foo() { .. }, // nome: foo
    *bar() { .. }, // nome: barra
    baz: () => { .. }, // nome: baz
    bam: function(){ .. }, // nome: bam
    obter o que() { .. }, // nome: obter o que
    set fuz() { .. }, // nome: set fuz
    ["b" + "iz"]:
      function(){ .. }, // nome: biz
    [Símbolo( "buz" )]:
      function(){ .. } // nome: [buz]
};

var x = o.foo.bind(o); // nome: vinculado foo
(função(){ .. }).bind( o ); // nome: vinculado
exportar função padrão() { .. } // nome: padrão
var y = nova Função(); //nome: anônimo
onde GeneratorFunction =
    função*(){}. proto .construtor;
var z = new GeneratorFunction(); //nome: anônimo

Por padrão, a propriedade name não é gravável, mas é configurável, o que significa que pode ser modificada manualmente usando Object.defineProperty(..) se necessário.

meta atributo

Metaatributos fornecem metainformações especiais na forma de acesso a atributos que não podem ser obtidas por outros métodos.

Tomando new.target como exemplo, a palavra-chave new é usada como contexto para acesso ao atributo. Obviamente, new em si não é um objeto, então esta função é muito especial. Quando new.target é usado dentro de uma chamada de construtor (uma função/método acionado por new), new se torna um contexto virtual, permitindo que new.target aponte para o construtor de destino que chama new.

Este é um exemplo claro de operação de metaprogramação, pois seu objetivo é determinar de dentro da chamada do construtor qual era o novo alvo original, geralmente falando para introspecção (verificação de tipo/estrutura) ou acesso a propriedades estáticas.

Por exemplo, você pode querer executar ações diferentes dentro de um construtor dependendo se ele é chamado diretamente ou por meio de uma subclasse:

classe Pai {
  construtor() {
    if (new.target === Pai) {
      console.log('Pai instanciado');
    } outro {
      console.log('Um filho instanciado');
    }
  }
}

classe Filho estende Pai {}

var a = new Pai();
// Pai instanciado

var b = novo Filho();
// Um ​​filho instanciado

O construtor() dentro da definição da classe Parent recebe, na verdade, o nome lexical da classe (Parent), embora a sintaxe implique que a classe é uma entidade separada do construtor.

símbolo público

JavaScript pré-define alguns símbolos integrados chamados símbolos públicos (Well-Known Symbol, WKS).

Esses símbolos são definidos principalmente para fornecer metapropriedades especializadas para que essas metapropriedades possam ser expostas a programas JavaScript para obter mais controle sobre o comportamento do JavaScript.

Símbolo.iterador

Symbol.iterator representa um local especial (atributo) em qualquer objeto. O mecanismo da linguagem encontra automaticamente um método neste local. Muitas definições de objeto possuem um valor padrão para este símbolo.

No entanto, você também pode definir sua própria lógica de iterador para valores arbitrários de objetos, definindo a propriedade Symbol.iterator, mesmo que isso substitua o iterador padrão. O aspecto da metaprogramação aqui é que definimos um atributo comportamental que pode ser usado por outras partes do JavaScript (ou seja, operadores e construções de loop) ao lidar com o objeto definido.

por exemplo:

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

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

// Defina um iterador que produza apenas valores em valores de índice ímpares
arr[Symbol.iterador] = função* () {
  onde idx = 1;
  fazer {
    produza isto[idx];
  } while ((idx += 2) <este.comprimento);
};

for (var v de arr) {
  console.log(v);
}
// 5 7 9

Símbolo.toStringTag e Símbolo.hasInstance

Uma das tarefas mais comuns de metaprogramação é examinar um valor para descobrir de que tipo ele é, geralmente para determinar quais operações são apropriadas para executar nele. Para objetos, as técnicas de introspecção mais comumente usadas são toString() e instanceof.

No ES6, você pode controlar o comportamento destas operações:

função Foo(saudação) {
  this.greeting = saudação;
}

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

Object.defineProperty(Foo, Símbolo.hasInstance, {
  valor: função (inst) {
    return inst.greeting == 'olá';
  },
});

var a = new Foo('olá'),
  b = novo Foo('mundo');

b[Symbol.toStringTag] = 'legal';

a.toString(); // [objeto Foo]
Sequência(b); // [objeto legal]
uma instância de Foo; // verdadeiro

b instância de Foo; // falso

A notação @@toStringTag do protótipo (ou da própria instância) especifica o valor da string usado quando [objeto] é stringificado.

A notação @@hasInstance é um método na função construtora que aceita um valor de objeto de instância e retorna verdadeiro ou falso para indicar se o valor pode ser considerado uma instância.

Símbolo.espécie

Qual construtor usar (Array(..) ou uma subclasse customizada) ao criar uma subclasse de Array e deseja definir métodos herdados (como slice(..)). Por padrão, chamar slice(..) em uma instância de uma subclasse Array cria uma nova instância desta subclasse.

Este requisito pode ser metaprogramado substituindo a definição padrão @@species de uma classe:

classe Legal {
  // Adia @@species para subclasses
  obtenção estática [Symbol.species]() {
    devolva isso;
  }

  de novo() {
    retornar novo this.constructor[Symbol.species]();
  }
}

classe Diversão estende Legal {}

class Impressionante estende Legal {
  //Força @@species a ser especificado como construtor pai
  obtenção estática [Symbol.species]() {
    retornar Legal;
  }
}

var a = nova diversão(),
  b = novo Incrível(),
  c = a.novamente(),
  d = b.de novo();

c instância de Diversão; // verdadeiro
d instância de Impressionante; // falso
d instância de Legal; // verdadeiro

O comportamento padrão de Symbol.species em construtores nativos integrados é retornar isso. Não há valor padrão na classe de usuário, mas como mostrado, esse recurso comportamental é fácil de simular.

Se você precisar definir um método para gerar novas instâncias, use a nova metaprogramação de padrão this.constructor[Symbol.species](..) em vez de codificar new this.constructor(..) ou new XYZ(..). As classes herdadas podem então personalizar Symbol.species para controlar qual construtor gera essas instâncias.

atuando

Um dos novos recursos de metaprogramação mais óbvios no ES6 é o recurso Proxy.

Um proxy é um objeto especial que você cria e que "encapsula" outro objeto comum - ou fica na frente desse objeto comum. Você pode registrar uma função de processamento especial (ou seja, trap) no objeto proxy. Este programa será chamado ao executar várias operações no proxy. Esses manipuladores têm a oportunidade de executar lógica adicional, além de encaminhar operações para o objeto de destino/encapsulado original.

Um exemplo de função manipuladora de trap que você pode definir em um proxy é get, que intercepta a operação [[Get]] quando você tenta acessar as propriedades de um objeto.

var obj = {a: 1},
  manipuladores = {
    get(alvo, chave, contexto) {
      // Nota: alvo === obj,
      // contexto === pobj
      console.log('acessando:', chave);
      return Reflect.get (destino, chave, contexto);
    },
  },
  pobj = novo Proxy(obj, manipuladores);

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

Declaramos um método de nomeação de função de processamento get(..) no objeto manipuladores (o segundo parâmetro do Proxy(..)), que aceita uma referência de objeto de destino (obj), nome de atributo chave ("a") e literais de corpo e eu/receptor/agente (pobj).

Limitações da agência

Um amplo conjunto de operações básicas que podem ser executadas em objetos pode ser tratada por meio dessas armadilhas de função de metaprogramação. Mas existem algumas operações que não podem (pelo menos por enquanto) ser interceptadas.

var obj = { a:1, b:2 },
manipuladores = { .. },
pobj = novo Proxy(obj, manipuladores);
tipo de objeto;
String(obj);

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

Resumir

Vamos resumir o conteúdo principal deste artigo:

  • Antes do ES6, o JavaScript já tinha muitas funções de metaprogramação, e o ES6 fornece vários novos recursos que melhoram significativamente as capacidades de metaprogramação.
  • Desde a derivação de nomes de funções para funções anônimas até metapropriedades que fornecem informações sobre como um construtor é chamado, você pode examinar mais profundamente a estrutura do tempo de execução do seu programa do que nunca. Ao expor símbolos, você pode substituir recursos originais, como a conversão de tipos de objetos em tipos nativos. Os proxies podem interceptar e personalizar várias operações subjacentes de objetos, e o Reflect fornece ferramentas para simulá-las.
  • O autor original recomenda: Primeiro, concentre-se em compreender como funciona o mecanismo central desta linguagem. E depois que você realmente entender como o JavaScript funciona, é hora de começar a usar esses poderosos recursos de metaprogramação para aplicar ainda mais a linguagem.

Clique para seguir e conhecer as novas tecnologias da Huawei Cloud o mais rápido possível~

Linus resolveu resolver o problema por conta própria para evitar que os desenvolvedores do kernel substituíssem tabulações por espaços. Seu pai é um dos poucos líderes que sabe escrever código, seu segundo filho é o diretor do departamento de tecnologia de código aberto e seu filho mais novo é um núcleo. contribuidor de código aberto Huawei: Demorou 1 ano para converter 5.000 aplicativos móveis comumente usados ​​A migração abrangente para Hongmeng Java é a linguagem mais propensa a vulnerabilidades de terceiros Wang Chenglu, o pai de Hongmeng: Hongmeng de código aberto é a única inovação arquitetônica. no campo de software básico na China. Ma Huateng e Zhou Hongyi apertam as mãos para "remover rancores". Ex-desenvolvedor da Microsoft: o desempenho do Windows 11 é "ridiculamente ruim" " Embora o que Laoxiangji seja de código aberto não seja o código, as razões por trás disso são muito emocionantes. Meta Llama 3 é lançado oficialmente. Google anuncia uma reestruturação em grande escala.
{{o.nome}}
{{m.nome}}

Acho que você gosta

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