Cenário 1: isso no ambiente global
Essa situação é relativamente simples e direta. A função é simplesmente chamada no ambiente global do navegador e this
apontada em modo não restrito window
; no use strict
caso de especificar o modo estrito, é undefined
:
function f1 () {
console.log(this)
}
function f2 () {
'use strict'
console.log(this)
}
f1() // window
f2() // undefined
Essas perguntas são relativamente básicas, mas se você estiver entrevistando, os candidatos precisam prestar atenção especial às suas variantes (por que sempre existem perguntas chatas e pervertidas), leia novamente:
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
var fn1 = foo.fn
fn1()
Aqui this
ainda está apontando para o arquivo window
. Embora fn
a função foo
seja referenciada como um método no objeto, após ser atribuída a ela fn1
, fn1
sua execução ainda ocorre no window
ambiente global. Portanto, a saída window
e undefined
, ainda o problema acima, se a chamada for alterada para:
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
foo.fn()
irá produzir:
{bar: 10, fn: ƒ}
10
Na verdade, isso pertence ao segundo caso, porque neste momento this
ele aponta para o objeto que o chamou por último e aponta para o objeto foo.fn()
na instrução . Lembre-se: Ao executar uma função, se o objeto na função for chamado pelo objeto de nível superior, ele apontará para o objeto de nível superior; caso contrário, ele apontará para o ambiente global.this
foo
this
this
Cenário 2: isso na chamada de objeto de contexto
Vamos olhar diretamente para o "difícil": quando há uma relação de chamada mais complicada,
const person = {
name: 'Lucas',
brother: {
name: 'Mike',
fn: function() {
return this.name
}
}
}
console.log(person.brother.fn())
Nesse relacionamento aninhado, this
ele aponta para o objeto que o chamou por último , então a saída será:Mike
Vejamos outro tópico mais complicado, por favor, prepare-se para o "exame" comigo:
const o1 = {
text: 'o1',
fn: function() {
return this.text
}
}
const o2 = {
text: 'o2',
fn: function() {
return o1.fn()
}
}
const o3 = {
text: 'o3',
fn: function() {
var fn = o1.fn
return fn()
}
}
console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())
A resposta é: o1
, o1
, undefined
, você acertou?
Vamos analisá-los um por um.
- O primeiro
console
é o mais fácil,o1
sem problemas. A dificuldade está no segundo e terceiro, a chave é olhar parathis
a função chamada. - O segundo
console
éo2.fn()
eventualmente chamadoo1.fn()
, então a resposta ainda é simo1
. - A última,
var fn = o1.fn
depois de fazer a atribuição, é a chamada "streaking", então o herethis
apontawindow
, é claro, para a respostaundefined
.
Se for na entrevista, como entrevistador, vou perguntar: Se precisar:
console.log(o2.fn())
saída o2
, o que fazer?
Desenvolvedores gerais podem pensar em usar bind/call/apply
para this
intervir na direção, o que é de fato uma forma de pensar. Mas aí eu perguntei, se não pode ser usado bind/call/apply
, tem outro jeito?
const o1 = {
text: 'o1',
fn: function() {
return this.text
}
}
const o2 = {
text: 'o2',
fn: o1.fn
}
console.log(o2.fn())
Ainda aplique a conclusão importante: this
aponte para o objeto que o chamou por últimofn
, apenas pendure-o no objeto durante a execução o2
e realizamos operações de atribuição semelhantes com antecedência.
Cenário 3: bind/call/apply muda a direção deste
O bind/call/apply mencionado acima:
const foo = {
name: 'lucas',
logName: function() {
console.log(this.name)
}
}
const bar = {
name: 'mike'
}
console.log(foo.logName.call(bar))
will output mike
, o que não é difícil de entender. Mas um exame avançado de call/apply/bind geralmente combina construtores e herança de implementação composicional. Falaremos sobre o tópico de implementação de herança separadamente. Para o caso de uso do construtor, nós o analisamos em combinação com os seguintes cenários.
Cenário 4: Construtor e este
Os exemplos mais diretos disso são:
function Foo() {
this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar)
A resposta será emitida Lucas
. Mas esse cenário geralmente é acompanhado pela próxima pergunta: new
o que exatamente o operador chama o construtor? O seguinte é para referência:
- criar um novo objeto;
- Aponte o construtor
this
para este novo objeto; - Adicione propriedades, métodos, etc. a este objeto;
- Finalmente retorna o novo objeto.
O processo acima também pode ser expresso em código:
var obj = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)
Claro que new
a simulação aqui é uma versão simples e básica, e não vou entrar em detalhes sobre esse assunto em casos mais complicados.
Cabe ressaltar que se houver uma situação explícita no construtor return
, então é necessário atentar para dois cenários:
function Foo(){
this.user = "Lucas"
const o = {}
return o
}
const instance = new Foo()
console.log(instance.user)
será a saída undefined
, caso em que instance
é o objeto vazio retornado o
.
function Foo(){
this.user = "Lucas"
return 1
}
const instance = new Foo()
console.log(instance.user)
será a saída Lucas
, o que significa que neste momento instance
é a instância do objeto de destino retornado this
.
Conclusão: Se o construtor retornar explicitamente um valor e um objeto, então this
aponta para o objeto retornado; se não retornar um objeto, this
ainda aponta para a instância.
Cenário 5: isso na função de seta aponta para
O uso de funções de seta this
não se aplica às regras padrão acima, mas é determinado de acordo com o escopo de contexto externo (função ou global).
Vejamos o tópico:
const foo = {
fn: function () {
setTimeout(function() {
console.log(this)
})
}
}
console.log(foo.fn())
Nesta questão, this
aparece na função anônimasetTimeout()
em , então aponta para o objeto. Se você precisar apontar para este objeto objeto, poderá usar a função de seta para resolvê-lo:this
window
this
foo
const foo = {
fn: function () {
setTimeout(() => {
console.log(this)
})
}
}
console.log(foo.fn())
// {fn: ƒ}
Na função de seta simples,this
é muito simples, mas considerando todas as situações e considerando this
a prioridade, this
não é fácil determinar a direção neste momento. Leia.
Cenário Final 6: Esta prioridade está relacionada
Frequentemente nos referimos à situação de ligação por meio de call
, apply
, bind
, new
para this
como ligação explícita; this
o apontar para determinado de acordo com o relacionamento de chamada é chamado de ligação implícita.
Então, qual deles tem maior prioridade, ligação explícita ou ligação implícita?
Veja exemplo:
function foo (a) {
console.log(this.a)
}
const obj1 = {
a: 1,
foo: foo
}
const obj2 = {
a: 2,
foo: foo
}
obj1.foo.call(obj2)
obj2.foo.call(obj1)
A saída é 2, 1, respectivamente, ou seja call
, apply
a ligação explícita de , geralmente tem prioridade mais alta que a ligação implícita.
function foo (a) {
this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)
O código acima passa bind
e vincula bar
a função this
como obj1
um objeto. Após a execução bar(2)
, obj1.a
o valor é 2. Ou seja, bar(2)
após a execução, obj1
o objeto é: {a: 2}
.
Quando reutilizado bar
como construtor:
var baz = new bar(3)
console.log(baz.a)
produzirá 3. Vemos que bar
a função em si é bind
uma função construída pelo método, que foi ligada dentro de this
, obj1
e é usada como construtor.Quando new
chamada por , a instância retornada foi obj1
desvinculada de . Quer dizer:
new
A vinculação modifica as vinculações no binding , portanto, as vinculações têm precedência maior do que as vinculações explícitas.bind
this
new
bind
Vejamos novamente:
function foo() {
return a => {
console.log(this.a)
};
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
const bar = foo.call(obj1)
console.log(bar.call(obj2))
produzirá 2. Como foo()
o of this
está vinculado a obj1
, bar
o of (referindo-se a uma função de seta) this
também está vinculado a obj1
, e a associação de uma função de seta não pode ser modificada.
Se foo
totalmente escrito como uma função de seta:
var a = 123
const foo = () => a => {
console.log(this.a)
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2))
irá produzir 123
.
Aqui vou "agitar meu cérebro" novamente, apenas mude a atribuição da primeira variável no código acima a
para:
const a = 123
const foo = () => a => {
console.log(this.a)
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2))
A resposta será gerada undefined
porque const
as variáveis declaradas com não são montadas window
no objeto global. Portanto, ao this
apontar para window
, naturalmente a
a variável não pode ser encontrada.