Jak działa „to” słowo kluczowe w funkcji?

248

Właśnie spotkałem ciekawą sytuację w JavaScript. Mam klasę z metodą, która definiuje kilka obiektów za pomocą notacji literałowej. Wewnątrz tych obiektów thisużywany jest wskaźnik. Na podstawie zachowania programu wydedukowałem, że thiswskaźnik odnosi się do klasy, na której wywołano metodę, a nie do obiektu tworzonego przez literał.

Wydaje się to arbitralne, ale tego właśnie oczekiwałbym. Czy to określone zachowanie? Czy to jest bezpieczne dla różnych przeglądarek? Czy jest jakieś uzasadnienie, dlaczego leży ono poza „specyfikacją tak mówi” (na przykład, czy jest to konsekwencja szerszej decyzji / filozofii projektu)? Przykład zredukowanego kodu:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}
rmeador
źródło
zdarza się, kiedy to robię var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons
sprawdź ten post . Ma dobre wyjaśnienie różnych zastosowań i zachowań tego słowa kluczowego.
Love Hasija
sprawdź
AL-zami

Odpowiedzi:

558

Cannibalized z innego mojego postu, oto więcej niż kiedykolwiek chciałeś o tym wiedzieć .

Zanim zacznę, najważniejsze jest, aby pamiętać o JavaScript i powtarzać sobie, gdy nie ma to sensu. JavaScript nie ma klas (ES6 classto cukier składniowy ). Jeśli coś wygląda jak klasa, to sprytna sztuczka. JavaScript ma obiekty i funkcje . (to nie jest w 100% dokładne, funkcje są tylko obiektami, ale czasem może być pomocne, aby traktować je jako osobne rzeczy)

Ta zmienna jest przyłączona do funkcji. Kiedykolwiek wywołać funkcję, to otrzymuje pewną wartość, w zależności od tego, jak wywołać funkcji. Jest to często nazywane wzorem wywoływania.

Istnieją cztery sposoby wywoływania funkcji w javascript. Możesz wywoływać funkcję jako metodę , jako funkcję , jako konstruktor i z zastosowaniem .

Jako metoda

Metoda to funkcja dołączona do obiektu

var foo = {};
foo.someMethod = function(){
    alert(this);
}

Wywołany jako metody, to będzie zobowiązany do obiektu funkcja / metoda jest częścią. W tym przykładzie będzie to związane z foo.

Jako funkcja

Jeśli masz funkcję samodzielną, ta zmienna zostanie powiązana z obiektem „globalnym”, prawie zawsze obiektem okna w kontekście przeglądarki.

 var foo = function(){
    alert(this);
 }
 foo();

To może Cię potknąć , ale nie czuj się źle. Wiele osób uważa to za złą decyzję projektową. Ponieważ wywołanie zwrotne jest wywoływane jako funkcja, a nie metoda, dlatego widzisz to, co wydaje się być niespójnym zachowaniem.

Wiele osób rozwiązuje ten problem, robiąc coś takiego

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

Definiujesz zmienną , która wskazuje na to . Zamknięcie (temat sam w sobie) utrzymuje to w pobliżu, więc jeśli wywołasz bar jako oddzwanianie, nadal ma on odniesienie.

UWAGA: W use stricttrybie, jeśli jest używany jako funkcja, thisnie jest powiązany z globalnym. (Tak jest undefined).

Jako konstruktor

Możesz także wywołać funkcję jako konstruktor. W oparciu o konwencję nazewnictwa, z której korzystasz (TestObject), może to być również to, co robisz i to Cię denerwuje .

Wywołujesz funkcję jako Konstruktor z nowym słowem kluczowym.

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

Wywołany jako konstruktor, nowy obiekt zostanie utworzony, a ten będzie związany z tym obiektem. Ponownie, jeśli masz funkcje wewnętrzne i są one używane jako wywołania zwrotne, będziesz wywoływać je jako funkcje, a to zostanie powiązane z obiektem globalnym. Użyj tego var that = ten trick / wzorzec.

Niektórzy uważają, że słowo kluczowe konstruktor / nowe było kością rzuconą na programistów Java / tradycyjnych programistów OOP jako sposób na stworzenie czegoś podobnego do klas.

Za pomocą metody Apply

Na koniec każda funkcja ma metodę (tak, funkcje są obiektami w JavaScript) o nazwie „zastosuj”. Zastosuj pozwala określić, jaka jest wartość tego będzie, a także pozwala przechodzić w tablicy argumentów. Oto bezużyteczny przykład.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
Alan Storm
źródło
8
Uwaga: W trybie ścisłym , thisbędzie undefineddla wywołań funkcji.
Miscreant,
1
Deklaracja funkcji, np. funkcja myfunction () {}, jest szczególnym przypadkiem „jako metody”, gdzie „this” jest zakresem globalnym (okno).
richard
1
@richard: Z wyjątkiem trybu ścisłego i thisnie ma nic wspólnego z zakresem. Masz na myśli obiekt globalny .
TJ Crowder
@ alan-storm. W przypadku „Jako konstruktor” jest this.confusing = 'hell yeah';taki sam jak var confusing = 'hell yeah';? Więc oba pozwolą myObject.confusing? Byłoby miło, gdyby nie tylko po to, aby można było użyć thisdo utworzenia właściwości i innych zmiennych do pracy wewnętrznej.
wunth
Ale znowu function Foo(thought){ this.confusing = thought; }var myObject = new Foo("hell yeah");
wydaje
35

Wywołania funkcji

Funkcje są tylko rodzajem obiektu.

Wszystkie obiekty Function mają metody wywołania i zastosowania , które wykonują wywołany obiekt Function.

Po wywołaniu pierwszy argument tych metod określa obiekt, do którego thissłowo kluczowe będzie się odnosić podczas wykonywania funkcji - jeśli jest to obiekt globalny nulllub undefined, do którego windowjest on używany this.

Wywołanie funkcji ...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

... z nawiasami - foo()- jest równoważne foo.call(undefined)lub foo.apply(undefined), co jest faktycznie takie samo jak foo.call(window)lub foo.apply(window).

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

Dodatkowe argumenty do callprzekazania są przekazywane jako argumenty do wywołania funkcji, podczas gdy pojedynczy dodatkowy argument do applyokreślenia argumentów dla wywołania funkcji jako obiekt podobny do tablicy.

Zatem foo(1, 2, 3)jest równoważne z foo.call(null, 1, 2, 3)lub foo.apply(null, [1, 2, 3]).

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

Jeśli funkcja jest własnością obiektu ...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

... dostęp do odwołania do funkcji za pośrednictwem obiektu i wywołanie go w nawiasach - obj.foo()- jest równoważne z foo.call(obj)lub foo.apply(obj).

Jednak funkcje utrzymywane jako właściwości obiektów nie są „powiązane” z tymi obiektami. Jak widać w powyższej definicji obj, ponieważ Funkcje są tylko rodzajem Object, można się do nich odwoływać (a zatem mogą być przekazywane przez odwołanie do wywołania funkcji lub zwracane przez odwołanie z wywołania funkcji). Gdy odwołanie do funkcji jest przekazywana bez dodatkowych informacji o tym, gdzie został przekazany od przeprowadza z nim, dlatego dodaje się dzieje:

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

Wywołanie naszego odwołania do funkcji baznie zapewnia żadnego kontekstu dla wywołania, więc jest faktycznie takie samo jak baz.call(undefined), więc thiskończy się odwołaniem window. Jeśli chcemy bazwiedzieć, że należy do tej domeny obj, musimy w jakiś sposób podać tę informację, kiedy bazzostanie wywołana, czyli tam, gdzie wchodzi pierwszy argument do calllub applyzamknięcie.

Łańcuchy zakresowe

function bind(func, context)
{
    return function()
    {
        func.apply(context, arguments);
    };
}

Kiedy funkcja jest wykonywana, tworzy nowy zakres i ma odniesienie do dowolnego zakresu obejmującego. Gdy funkcja anonimowa jest tworzona w powyższym przykładzie, ma odniesienie do zakresu, w którym została utworzona, czyli bindzakresu. Jest to znane jako „zamknięcie”.

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

Kiedy próbujesz uzyskać dostęp do zmiennej, ten „łańcuch zasięgu” idzie do znalezienia zmiennej o podanej nazwie - jeśli bieżący zakres nie zawiera zmiennej, patrzysz na następny zakres w łańcuchu i tak dalej, aż dojdziesz zasięg globalny. Kiedy funkcja anonimowa jest zwracana i bindkończy działanie, funkcja anonimowa nadal ma odniesienie do bindzakresu, więc bindzakres nie „znika”.

Biorąc pod uwagę wszystkie powyższe, powinieneś być teraz w stanie zrozumieć, jak działa zakres w poniższym przykładzie i dlaczego technika przekazywania funkcji wokół „wstępnie powiązanej” z określoną wartością thisbędzie miała, gdy zostanie nazwana:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"
Jonny Buchanan
źródło
„Po przekazaniu odwołania do funkcji nie są przenoszone żadne dodatkowe informacje o tym, skąd została ona przekazana”. Dziękujemy za to @insin.
Alex Marandon,
9

Czy to określone zachowanie? Czy to jest bezpieczne dla różnych przeglądarek?

Tak. I tak.

Czy jest jakieś uzasadnienie, dlaczego tak jest ...

Znaczenie tego thisjest dość proste, aby wydedukować:

  1. Jeśli thisjest używany wewnątrz funkcji konstruktora, a funkcja została wywołana ze newsłowem kluczowym, thisoznacza obiekt, który zostanie utworzony. thisbędzie nadal oznaczać obiekt nawet w metodach publicznych.
  2. Jeśli thisjest używany gdziekolwiek indziej, w tym zagnieżdżone funkcje chronione , odnosi się do zasięgu globalnego (który w przypadku przeglądarki jest obiektem okna).

Drugi przypadek jest oczywiście wadą projektową, ale dość łatwo można go obejść za pomocą zamknięć.

Rakesh Pai
źródło
4

W tym przypadku wnętrze thisjest powiązane z obiektem globalnym zamiast ze thiszmienną funkcji zewnętrznej. Jest to sposób projektowania języka.

Dobre wyjaśnienie można znaleźć w „JavaScript: The Good Parts” Douglasa Crockforda.

Santiago Cepas
źródło
4

Znalazłem fajny samouczek na temat ECMAScript

Ta wartość jest specjalnym obiektem związanym z kontekstem wykonania. Dlatego może być nazwany jako obiekt kontekstu (tj. Obiekt, w którym kontekście uaktywniony jest kontekst wykonania).

Jako tę wartość kontekstu można użyć dowolnego obiektu.

ta wartość jest właściwością kontekstu wykonania, ale nie jest właściwością obiektu zmiennej.

Ta funkcja jest bardzo ważna, ponieważ w przeciwieństwie do zmiennych, ta wartość nigdy nie bierze udziału w procesie rozpoznawania identyfikatora. Oznacza to, że podczas uzyskiwania dostępu do tego w kodzie jego wartość jest pobierana bezpośrednio z kontekstu wykonania i bez wyszukiwania łańcucha zasięgu. Wartość tego jest określana tylko raz podczas wchodzenia w kontekst.

W kontekście globalnym ta wartość jest samym obiektem globalnym (co oznacza, że ​​ta wartość tutaj jest równa obiektowi zmiennemu)

W przypadku kontekstu funkcji ta wartość w każdym wywołaniu funkcji może być inna

Odwołaj się do Javascript-the-core i Rozdział-3-this

Damodaran
źródło
W kontekście globalnym tą wartością jest sam obiekt globalny (co oznacza, że ​​ta wartość tutaj jest równa obiektowi zmiennemu) ”. Obiekt globalny jest częścią globalnego kontekstu wykonania, podobnie jak „obiekt zmienny” (es4) i rekord środowiska ES5. Są to jednak inne podmioty niż obiekt globalny (np. Rekord środowiska nie może być bezpośrednio przywołany, jest to zabronione przez specyfikację, ale obiekt globalny może być).
RobG