JavaScript: co to znaczy?

Ustalenie wartości this może być trudne w JavaScript. Oto jak to zrobić:

Jake Archibald
Jake Archibald

this jest obiektem wielu żartów, ponieważ jest dość skomplikowana. Jednak widzimy, że deweloperzy stosują znacznie bardziej skomplikowane rozwiązania, aby uniknąć korzystania z tego this. Jeśli nie masz pewności co do this, mam nadzieję, że te informacje okażą się przydatne. To mój przewodnik this.

Zaczniemy od najbardziej konkretnej sytuacji, a skończymy na najmniej konkretnej. Ten artykuł jest trochę jak duża tabela if (…) … else if () … else if (…) …, więc możesz przejść bezpośrednio do pierwszej sekcji, która pasuje do kodu, który Cię interesuje.

  1. Jeśli funkcja jest zdefiniowana jako funkcja strzałki
  2. W przeciwnym razie, jeśli funkcja lub klasa jest wywoływana z new
  3. W przeciwnym razie, jeśli funkcja ma wartość „bound” this
  4. W przeciwnym razie, jeśli this jest ustawiony na czas rozmowy
  5. W przeciwnym razie, jeśli funkcja jest wywoływana za pomocą obiektu nadrzędnego (parent.func())
  6. W przeciwnym razie, jeśli funkcja lub zakres nadrzędny jest w trybie ścisłym
  7. W innym przypadku

Jeśli funkcja jest zdefiniowana jako funkcja strzałki:

const arrowFunction = () => {
  console
.log(this);
};

W tym przypadku wartość this jest zawsze taka sama jak this w zakresie nadrzędnym:

const outerThis = this;

const arrowFunction = () => {
 
// Always logs `true`:
  console
.log(this === outerThis);
};

Funkcje strzałek są świetne, ponieważ wewnętrzna wartość funkcji this nie może się zmienić. Jest zawsze taka sama jak zewnętrzna wartość this.

Inne przykłady

W przypadku funkcji strzałki wartości this nie można zmienić za pomocą funkcji bind:

// Logs `true` - bound `this` value is ignored:
arrowFunction
.bind({foo: 'bar'})();

W przypadku funkcji strzałki wartości this nie można zmienić za pomocą funkcji call ani apply:

// Logs `true` - called `this` value is ignored:
arrowFunction
.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction
.apply({foo: 'bar'});

W przypadku funkcji strzałki wartości this nie można zmienić przez wywołanie funkcji jako elementu innego obiektu:

const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj
.arrowFunction();

W przypadku funkcji strzałki wartości this nie można zmienić, wywołując funkcję jako konstruktor:

// TypeError: arrowFunction is not a constructor
new arrowFunction();

Metody instancji „związane”

Jeśli w przypadku metod instancji chcesz mieć pewność, że this zawsze odnosi się do instancji klasy, najlepiej użyć funkcji strzałki i pól klasy:

class Whatever {
  someMethod
= () => {
   
// Always the instance of Whatever:
    console
.log(this);
 
};
}

Ten wzór jest bardzo przydatny, gdy używasz metod instancji jako detektorów zdarzeń w komponentach (takich jak komponenty React lub komponenty sieciowe).

Powyższe może wyglądać na naruszenie reguły, że „this jest takie samo jak this w zakresie nadrzędnym”, ale zaczyna to nabierać sensu, gdy potraktujesz pola klasy jako element składnikowy do ustawiania wartości w konstruktorze:

class Whatever {
  someMethod
= (() => {
   
const outerThis = this;
   
return () => {
     
// Always logs `true`:
      console
.log(this === outerThis);
   
};
 
})();
}

// …is roughly equivalent to:

class Whatever {
  constructor
() {
   
const outerThis = this;
   
this.someMethod = () => {
     
// Always logs `true`:
      console
.log(this === outerThis);
   
};
 
}
}

Alternatywnym wzorcem jest wiązanie istniejącej funkcji w konstruktorze lub przypisywanie funkcji w konstruktorze. Jeśli z jakiegoś powodu nie możesz używać pól klasy, przypisanie funkcji w konstruktorze jest rozsądną alternatywą:

class Whatever {
  constructor
() {
   
this.someMethod = () => {
     
// …
   
};
 
}
}

W przeciwnym razie, jeśli funkcja lub klasa jest wywoływana za pomocą new:

new Whatever();

Powyższy kod wywoła funkcję Whatever (lub jej konstruktor, jeśli jest to klasa) z wartością this równą wynikowi funkcji Object.create(Whatever.prototype).

class MyClass {
  constructor
() {
    console
.log(
     
this.constructor === Object.create(MyClass.prototype).constructor,
   
);
 
}
}

// Logs `true`:
new MyClass();

To samo dotyczy starszych konstruktorów:

function MyClass() {
  console
.log(
   
this.constructor === Object.create(MyClass.prototype).constructor,
 
);
}

// Logs `true`:
new MyClass();

Inne przykłady

Gdy funkcja jest wywoływana za pomocą new, wartości this nie można zmienić za pomocą bind:

const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();

Gdy funkcja jest wywoływana za pomocą new, wartości this nie można zmienić, wywołując funkcję jako element innego obiektu:

const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();

Jeśli jednak funkcja ma wartość „bound” this:

function someFunction() {
 
return this;
}

const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);

Gdy wywoływana jest funkcja boundFunction, jej parametr this będzie obiektem przekazywanym do funkcji bind (boundObject).

// Logs `false`:
console
.log(someFunction() === boundObject);
// Logs `true`:
console
.log(boundFunction() === boundObject);

Inne przykłady

Podczas wywoływania funkcji ograniczonej wartości this nie można zmienić za pomocą instrukcji call ani apply:

// Logs `true` - called `this` value is ignored:
console
.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console
.log(boundFunction.apply({foo: 'bar'}) === boundObject);

Podczas wywoływania funkcji powiązanej wartości this nie można zmienić, wywołując funkcję jako element innego obiektu:

const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console
.log(obj.boundFunction() === boundObject);

W przeciwnym razie, jeśli this jest ustawiony w momencie połączenia:

function someFunction() {
 
return this;
}

const someObject = {hello: 'world'};

// Logs `true`:
console
.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console
.log(someFunction.apply(someObject) === someObject);

Wartość this to obiekt przekazany do call/apply.

Niestety this jest ustawiany na inną wartość przez takie elementy jak odbiorniki zdarzeń DOM, a jego użycie może spowodować powstanie kodu, który jest trudny do zrozumienia:

Nie
element.addEventListener('click', function (event) {
 
// Logs `element`, since the DOM spec sets `this` to
 
// the element the handler is attached to.
  console
.log(this);
});

W takich przypadkach unikam stosowania this, a zamiast tego:

Tak
element.addEventListener('click', (event) => {
 
// Ideally, grab it from a parent scope:
  console
.log(element);
 
// But if you can't do that, get it from the event object:
  console
.log(event.currentTarget);
});

Jeśli funkcja jest wywoływana za pomocą obiektu nadrzędnego (parent.func()), wykonaj jedną z tych czynności:

const obj = {
  someMethod
() {
   
return this;
 
},
};

// Logs `true`:
console
.log(obj.someMethod() === obj);

W tym przypadku funkcja jest wywoływana jako element obiektu obj, więc this będzie równe obj. Dzieje się tak w momencie wywołania, więc połączenie jest zerwane, jeśli funkcja zostanie wywołana bez obiektu nadrzędnego lub z innym obiektem nadrzędnym:

const {someMethod} = obj;
// Logs `false`:
console
.log(someMethod() === obj);

const anotherObj = {someMethod};
// Logs `false`:
console
.log(anotherObj.someMethod() === obj);
// Logs `true`:
console
.log(anotherObj.someMethod() === anotherObj);

someMethod() === obj ma wartość fałsz, ponieważ someMethod nie jest wywoływany jako element obj. Ten problem może wystąpić, gdy spróbujesz wykonać jedną z tych czynności:

const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');

To powoduje błąd, ponieważ implementacja querySelector sprawdza własną wartość this i oczekuje, że będzie to węzeł DOM. Powyższe działanie powoduje przerwanie tego połączenia. Aby uzyskać prawidłowe wyniki:

const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);

Ciekawostka: nie wszystkie interfejsy API używają this wewnętrznie. Metody konsoli, takie jak console.log, zostały zmienione, aby uniknąć odwołań do this, więc log nie musi być powiązana z console.

Jeśli jednak zakres funkcji lub zakres nadrzędny jest w trybie ścisłym:

function someFunction() {
 
'use strict';
 
return this;
}

// Logs `true`:
console
.log(someFunction() === undefined);

W tym przypadku wartość this jest niezdefiniowana. W funkcja nie jest potrzebna zmienna 'use strict', jeśli zakres nadrzędny jest w trybie ścisłym (a wszystkie moduły są w tym trybie).

W innym przypadku:

function someFunction() {
 
return this;
}

// Logs `true`:
console
.log(someFunction() === globalThis);

W tym przypadku wartość this jest taka sama jak wartość globalThis.

Uff...

To wszystko. To wszystko, co wiem o this. Masz pytania? Czy coś mi umknęło? Wyślij mi tweeta.

Dziękujemy Mathiasowi Bynensowi, Ingvarowi StepanyanowiThomasowi Steinerowi za sprawdzenie.