O que é isso aqui? Operação interna de objetos JavaScript


Foto: Curiosa Liliana Saeb (CC BY 2.0)


JavaScript é uma linguagem de vários paradigmas que suporta programação orientada a objetos e vinculação dinâmica. A vinculação dinâmica é um conceito poderoso que permite alterar a estrutura do código JavaScript em tempo de execução, mas esse poder e flexibilidade adicionais são obtidos à custa de alguma confusão, a maioria relacionada a this comportamento no JavaScript.


Ligação dinâmica


Com a ligação dinâmica, a definição do método a ser chamado ocorre no tempo de execução e não no tempo de compilação. O JavaScript faz isso com this e com a cadeia de protótipos. Em particular, dentro do método, this é determinado durante a chamada e o valor this será diferente dependendo de como o método foi definido.


Vamos jogar um jogo. Eu a chamo de "O que é this aqui?"


 const a = { a: 'a' }; const obj = { getThis: () => this, getThis2 () { return this; } }; obj.getThis3 = obj.getThis.bind(obj); obj.getThis4 = obj.getThis2.bind(obj); const answers = [ obj.getThis(), obj.getThis.call(a), obj.getThis2(), obj.getThis2.call(a), obj.getThis3(), obj.getThis3.call(a), obj.getThis4(), obj.getThis4.call(a) ]; 

Pense em quais answers os valores na matriz de answers e verifique suas respostas com console.log() . Adivinhou?


Vamos começar com o primeiro caso e continuar em ordem. obj.getThis() retorna undefined , mas por quê? As funções de seta nunca têm this próprio. Em vez disso, eles sempre tiram this do escopo lexical ( aprox. Lexical isso ). Para a raiz do módulo ES6, a região lexical terá um valor undefined this . obj.getThis.call(a) também não está definido pelo mesmo motivo. Para funções de seta, this não pode ser substituído, mesmo com .call() ou .bind() . this sempre será retirado do domínio lexical.


obj.getThis2() obtém a ligação durante a chamada do método. Se não havia essa ligação para essa função antes, isso pode ser vinculado a this (já que essa não é uma função de seta). Nesse caso, this é um objeto obj vinculado no momento em que o método é chamado . ou sintaxe de acesso à propriedade [squareBracket] . ( observe ligação implícita )


obj.getThis2.call(a) pouco mais complicado. O método call() chama uma função com esse valor fornecido e argumentos opcionais. Em outras palavras, o método obtém a ligação this do parâmetro .call() , então obj.getThis2.call(a) retorna o objeto a . ( observe ligação explícita )


No caso de obj.getThis3 = obj.getThis.bind(obj); estamos tentando obter uma função de seta com this limite, que, como já descobrimos, não funcionará, portanto, somos undefined para obj.getThis3() e obj.getThis3.call(a) respectivamente.


Para métodos regulares, você pode vincular, então obj.getThis4() retorna obj , conforme o esperado. Ele já conseguiu sua ligação aqui obj.getThis4 = obj.getThis2.bind(obj); , e obj.getThis4.call(a) leva em consideração a primeira ligação e retorna obj vez de a .


Bola torcida


Resolveremos o mesmo problema, mas desta vez usamos uma class com campos públicos para descrever o objeto (as inovações do Estágio 3 no momento da redação deste texto estão disponíveis no Chrome por padrão e com @babel/plugin-offer-class-properties ):


 class Obj { getThis = () => this getThis2 () { return this; } } const obj2 = new Obj(); obj2.getThis3 = obj2.getThis.bind(obj2); obj2.getThis4 = obj2.getThis2.bind(obj2); const answers2 = [ obj2.getThis(), obj2.getThis.call(a), obj2.getThis2(), obj2.getThis2.call(a), obj2.getThis3(), obj2.getThis3.call(a), obj2.getThis4(), obj2.getThis4.call(a) ]; 

Pense nas respostas antes de continuar.


Você está pronta?


Todas as chamadas, exceto obj2.getThis2.call(a) retornam uma instância do objeto. obj2.getThis2.call(a) retorna a . As funções de seta ainda conseguem this em seu ambiente lexical. Há uma diferença em como this é definido no ambiente lexical para propriedades de classe. Por dentro, a inicialização das propriedades da classe é mais ou menos assim:


 class Obj { constructor() { this.getThis = () => this; } ... 

Em outras palavras, a função de seta é definida dentro do contexto do construtor. Como essa é uma classe, a única maneira de criar uma instância é usar a new palavra-chave (a omissão de new resultará em erro). Uma das coisas mais importantes que a new palavra-chave faz é criar uma nova instância do objeto e vincular this a ele no construtor. Esse comportamento, combinado com outros comportamentos que mencionamos acima, deve explicar o resto.


Conclusão


Você conseguiu? Uma boa compreensão de como this se comporta no JavaScript poupará muito tempo na depuração de problemas complexos. Se você cometeu um erro nas respostas, isso significa que você precisa praticar um pouco. Pratique com exemplos, depois volte e teste a si mesmo novamente até poder executar o teste e explicar a outra pessoa por que os métodos retornam o que retornam.


Se foi mais difícil do que você esperava, então você não está sozinho. Perguntei a muitos desenvolvedores sobre esse tópico e acho que até agora apenas um deles lidou com essa tarefa.


O que começou como uma pesquisa de métodos dinâmicos que você pode redirecionar com .call() , .bind() ou .apply() se tornou muito mais complicado com a adição de métodos de classe e funções de seta. Talvez você deva se concentrar mais uma vez nisso. Lembre-se de que as funções de seta sempre tiram this do escopo lexical e class são lexicamente limitadas pelo construtor de classes sob o capô. Se você duvidar this , lembre-se de que pode usar um depurador para verificar seu valor.


Lembre-se de que, ao resolver muitas tarefas JavaScript, você pode fazer this sem this . Na minha experiência, quase tudo pode ser redefinido em termos de funções puras que levam todos os argumentos usados ​​como parâmetros explícitos ( this pode ser pensado como uma variável implícita). A lógica descrita através de funções puras é determinística, o que a torna mais testável. Além disso, com essa abordagem, não há efeitos colaterais, portanto, diferentemente dos momentos de manipulação, é improvável que você quebre alguma coisa. Toda vez que this definido, algo dependendo do seu valor pode quebrar.


No entanto, às vezes this é útil. Por exemplo, para trocar métodos entre um grande número de objetos. Mesmo na programação funcional, this pode ser usado para acessar outros métodos de objetos, a fim de implementar as transformações algébricas necessárias para construir novas álgebras sobre as já existentes. Portanto, universal .flatMap() pode ser obtido usando this.map() e this.constructor.of() .




Obrigado pela ajuda na tradução de wksmirnowa e VIBaH_dev

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


All Articles