Dlaczego metoda zwraca obiekt "window" jako "this"?

0

Zastanawiam się, dlaczego metoda private zwraca obiekt window. Metoda public2 zwraca obiekt, na rzecz którego została wywołana, i właśnie tego się spodziewałem. Dlaczego metoda public1 nie przekazuje metodzie private referencji do tego samego obiektu jako niejawny parametr this? Rozumiem, że gdyby funkcja była zdefiniowana w obiekcie Window i wywołana jako funkcja, to this wskazywałoby na window, ale w jaki sposób obiekt window znalazł się w moim przykładzie?

function F()
{
  var private = function()
  { 
    return this;
  }
  
  this.public1 = function()
  {
    if (private() === window) //true
      console.log("private() === window");
  }
  
  this.public2 = function()
  {
    return this;
  }
}
    
var f = new F();
f.public1();
if (f === f.public2()) //true
  console.log("f === f.public2()");

Czy dobrze myślę, że tworzenie obiektu f w przykładzie powyżej przebiega w taki sam sposób, jak tworzyłbym obiekt ręcznie, a nie wykonywał funkcję jako konstruktor?

var f = {};
f.public1 = function...
f.public2 = function...

Czym jest więc metoda private w takiej interpretacji?
Następnym problemem jest brak widoczności metod i zmiennych publicznych wewnątrz metod prywatnych. Nie wiem, czy aż tyle można oczekiwać od JavaScript, ale takie zachowanie wydaje mi się w tym momencie nielogiczne i chciałbym to zrozumieć. Metoda prywatna zachowuje się trochę tak, jakby była statyczna.

4

W javascript this ma tzw. późne wiązanie i zależy od tego jak wywołasz swoją funkcję i skąd. W zasadzie możesz każdą funkcję traktować jako zwykły osobny byt, bo this nie jest automatycznie bindowane tak jak w innych językach.

var foo = {
  fn: function() {
    console.log("Funckja foo", this);
  }
}

foo.fn();
var nieFoo = foo.fn; 
nieFoo();
// Funckja foo Object {} wywołana na rzecz obiektu foo
// Funckja foo Window "pożyczona" funkcja już nie ma this pokazującego na foo!

Równie dobrze powyższy kod możemy przedstawić w ten sposób:

var someFunction = function() {
    console.log("Funckja foo", this);
};
 
var foo = {
  fn: someFunction
}

foo.fn();
var nieFoo = foo.fn; 
nieFoo();
// Funckja foo Object {} wywołana na rzecz obiektu foo
// Funckja foo Window "pożyczona" funkcja już nie ma this pokazującego na foo!

Jak widać otrzymamy to samo.

Ogólnie wygląda to tak:

1. Czy funkcja jest wywołana z operatorem new (new binding)? Jeżeli tak, to this to jest nowo utworzony obiekt.

var bar = new foo();

**2. **Czy funkcja jest wywołana z użyciem call/apply (explicit binding)? Jeżeli tak, to this wewnątrz tej funkcji to będzie ten obiekt czyli obj2.

var bar = foo.call( obj2 )

**3. **Czy funkcja jest wywołana na rzecz jakiegoś obiektu (implicit binding)? Jeżeli tak, to this będzie tym obiektem.

var bar = obj1.foo()

**4. **W każdym innym przypadku zachodzi default binding, czyli this jest obiektem globalnym (chyba, że jest to strict mode to wtedy undefined).

var bar = foo()

W twoim przypadku podczas wywołania metody private zachodzi warunek 4. This nie działa kaskadowo.

var foo = {
  bar: function() {
    var baz = function () {
      console.log("Baz ", this);
    };

    console.log("Bar ", this); 
    
    baz();
  }
};

foo.bar();
// Bar  Object {} ponieważ zachodzi zasada 3, wywołujemy bar na rzecz obiektu foo
// Baz  Window ponieważ zachodzi zasada 4.

Wracając do twojego przykładu, this w metodzie prywatnej zawsze wskazuje na window przez zagnieżdzenie. Jeżeli chcemy, żeby to zadziałało musimy wyraźnie zaznaczyć poprzez użycie bind, że chcemy aby this pokazywało na F.

function F()
{
  var private = function()
  { 
    return this;
  }.bind(this); // wymuszamy, żeby this wewnątrz pokazywał na this, czyli F
 
  this.public1 = function()
  {
    if (private() === window) //true
      console.log("private() === window");
  }
 
  this.public2 = function()
  {
    return this;
  }
}
var f = new F();
f.public1();
// undefined
f.public2();
// F {}

Jeżeli chodzi o to:

var f = {};
f.public1 = function...
f.public2 = function...

Użycie słowa new na funckji w javascript powoduje 4 rzeczy:

  1. Tworzy nowy obiekt (taki zwykły, pusty var f = {};).
  2. Ustawia prototype na prototyp funkcji konstruktora.
  3. Wywołuje funkcję konstruktora i używa nowego obiektu z pkt. 1 jako this
  4. Zwraca nowo utworzony obiekt. Może to być obiekt, który funkcja sama stworzy, ale możemy również zwrócić nasz własny!
function F() {
  var API = {
    sayHi: function() {
      console.log("Hello");
    }
  }

  return API;
}

function Fn() {
  this.sayHi = function() {
      console.log("Hello");
  }
}

var f = new F();
var fn = new Fn();

f.sayHi();
fn.sayHi();

Więc tak, z tego co wiem jest to identyczne z wywołaniem funkcji konstruktora

Warto dodać, że js nie implementuje kontroli dostępu, ale możemy ją uzyskać korzystając z Closure, czyli w skrócie z tego, że każda funkcja ma dodatkowo dostęp do "środowiska" ją otaczającego.

Do dalszego poczytania:
https://github.com/getify/You-Dont-Know-JS

1

@Desu, dzięki za wyczerpującą odpowiedź :) You Don't Know JS wygląda interesująco, zamierzam przeczytać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1