JavaScript: Bunun anlamı nedir?

JavaScript'te this değerini anlamak zor olabilir. Nasıl yapıldığını buradan öğrenebilirsiniz...

Jake Archibald
Jake Archibald

JavaScript'in this, şakanın en önemli kısmıdır ve bu nedenle oldukça karmaşıktır. Ancak, geliştiricilerin bu this sorunuyla karşılaşmamak için çok daha karmaşık ve alana özgü işlemler yaptığını gözlemledim. this konusunda emin değilseniz bu bilginin işinize yarayacağını umuyorum. Bu benim this rehberim.

En ayrıntılı şekilde başlayıp en az spesifik olanla bitireceğim. Bu makale büyük bir if (…) … else if () … else if (…) … gibi olduğundan doğrudan baktığınız kodla eşleşen ilk bölüme gidebilirsiniz.

  1. İşlev bir ok işlevi olarak tanımlanırsa
  2. Aksi halde, işlev/sınıf new ile çağrılırsa
  3. Aksi halde, işlevin "bağlı" this değeri varsa
  4. Aksi takdirde, this arama anında ayarlanırsa
  5. Aksi halde, işlev bir üst nesne (parent.func()) aracılığıyla çağrılırsa
  6. Aksi halde işlev veya üst kapsam, yüksek düzey modundaysa
  7. Aksi halde

İşlev bir ok işlevi olarak tanımlanırsa:

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

Bu durumda, this değeri her zaman üst kapsamdaki this ile aynıdır:

const outerThis = this;

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

Ok işlevleri harikadır çünkü this iç değeri değiştirilemez, dış this ile her zaman aynıdır.

Diğer örnekler

Ok işlevleri kullanıldığında this değeri bind ile değiştirilemez:

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

Ok işlevleri kullanıldığında this değeri call veya apply ile değiştirilemez:

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

Ok işlevlerinde this değeri, işlev başka bir nesnenin üyesi olarak çağrılarak değiştirilemez:

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

Ok işlevlerinde this değeri, işlev bir kurucu olarak çağrılarak değiştirilemez:

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

"Bağlı" örnek yöntemleri

Örnek yöntemleriyle, this öğesinin her zaman sınıf örneğine başvurduğundan emin olmak istiyorsanız en iyi yol, ok işlevlerini ve sınıf alanlarını kullanmaktır:

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

Bu kalıp, bileşenlerde (React bileşenleri veya web bileşenleri gibi) olay işleyiciler olarak örnek yöntemleri kullanırken gerçekten yararlıdır.

Yukarıdaki yöntem, "this, üst kapsamda this ile aynı olacak" kuralını ihlal ediyormuş gibi görünebilir, ancak sınıf alanlarını, oluşturucuda bazı öğeleri ayarlamak için kullanılan sözdizimsel şeker olarak düşünürseniz bu durum anlamlı olmaya başlar:

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);
    };
  }
}

Alternatif yamalar, oluşturucuda mevcut bir işlevi bağlamayı veya işlevin oluşturucuda atanmasını içerir. Herhangi bir nedenle sınıf alanlarını kullanamıyorsanız oluşturucuda işlev atamak makul bir alternatiftir:

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

Aksi takdirde, işlev/sınıf new ile çağrılırsa:

new Whatever();

Yukarıdaki kural, this parametresinin Object.create(Whatever.prototype) sonucuna ayarlanmış olarak Whatever (veya sınıfsa oluşturucu işlevini) çağırır.

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

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

Aynı durum, eski tarz oluşturucular için de geçerlidir:

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

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

Diğer örnekler

new ile çağrıldığında this değeri bind ile değiştirilemez:

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

new ile çağrıldığında, this değeri işlev başka bir nesnenin üyesi olarak çağrılarak değiştirilemez:

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

Aksi takdirde, işlevin "bağlı" bir this değeri varsa:

function someFunction() {
  return this;
}

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

boundFunction her çağrıldığında, this değeri bind (boundObject) öğesine geçirilen nesne olur.

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

Diğer örnekler

Bir bağlı işlev çağrılırken this değeri call veya apply ile değiştirilemez:

// 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);

Bir bağlı işlev çağrılırken this değeri, işlev başka bir nesnenin üyesi olarak çağrılarak değiştirilemez:

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

Aksi takdirde, this arama zamanında ayarlanırsa:

function someFunction() {
  return this;
}

const someObject = {hello: 'world'};

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

this değeri, call/apply işlevine iletilen nesnedir.

Maalesef this, DOM etkinlik işleyicileri gibi şeyler tarafından başka bir değere ayarlandığı için kullanılması zor bir koda neden olabilir:

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

Yukarıdaki gibi durumlarda this kullanmaktan kaçınıyorum ve bunun yerine:

Yapılması gerekenler
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);
});

Aksi takdirde, işlev bir üst nesne (parent.func()) aracılığıyla çağrılırsa:

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

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

Bu örnekte, işlev obj öğesinin bir üyesi olarak çağrılır; dolayısıyla this, obj olur. Bu durum, çağrı zamanında gerçekleşir. Bu nedenle, işlev üst nesnesi olmadan veya farklı bir üst nesne ile çağrılırsa bağlantı bozulur:

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 üyesi olarak çağrılmadığından someMethod() === obj yanlıştır. Şuna benzer bir şey denerken bu hata ile karşılaşmış olabilirsiniz:

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

Bu hata, querySelector uygulamasının kendi this değerine bakması, bunun bir tür DOM düğümü olmasını beklediği ve yukarıdaki bağlantı bu bağlantıyı koparmasına neden olur. Yukarıdakileri doğru şekilde gerçekleştirmek için:

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

Eğlenceli bir gerçek: Tüm API'ler dahili olarak this kullanmaz. console.log gibi konsol yöntemleri, this referanslarını önleyecek şekilde değiştirilmiştir. Bu nedenle, log öğesinin console öğesine bağlanmasına gerek yoktur.

Aksi takdirde, işlev veya üst kapsam yüksek düzey modundaysa:

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

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

Bu durumda, this değeri tanımsızdır. Üst kapsam yüksek düzey modunda (ve tüm modüller yüksek düzey modundaysa) işlevde 'use strict' gerekli değildir.

Diğer durumlarda:

function someFunction() {
  return this;
}

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

Bu durumda, this değeri globalThis ile aynıdır.

Bora

Hepsi bu kadar! this hakkında bildiğim tüm bu yok. Sorunuz mu var? Atladığımız bir şey var mı? İsterseniz bana tweet atabilirsiniz.

İncelemeleri için Mathias Bynens, Ingvar Stepanyan ve Thomas Steiner'a teşekkür ederiz.