JavaScript: Apa artinya?

Mengetahui nilai this bisa jadi sulit dalam JavaScript, berikut cara melakukannya...

Archibald Jake
Jake Archibald

this JavaScript adalah awal dari banyak lelucon, dan itu karena memang cukup rumit. Namun, saya melihat developer melakukan hal yang jauh lebih rumit dan khusus domain agar tidak menangani this ini. Jika Anda tidak yakin tentang this, semoga ini dapat membantu. Ini adalah panduan this saya.

Saya akan mulai dengan situasi yang paling spesifik, dan mengakhirinya dengan yang paling tidak spesifik. Artikel ini mirip seperti if (…) … else if () … else if (…) … besar, jadi Anda dapat langsung ke bagian pertama yang cocok dengan kode yang sedang Anda lihat.

  1. Jika fungsi ditentukan sebagai fungsi panah
  2. Atau, jika fungsi/class dipanggil dengan new
  3. Atau, jika fungsi memiliki nilai this 'terikat'
  4. Atau, jika this disetel pada waktu panggilan
  5. Atau, jika fungsi dipanggil melalui objek induk (parent.func())
  6. Atau, jika fungsi atau cakupan induk berada dalam mode ketat
  7. Atau

Jika fungsi ditentukan sebagai fungsi panah:

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

Dalam hal ini, nilai this selalu sama dengan this dalam cakupan induk:

const outerThis = this;

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

Fungsi panah sangat bagus karena nilai dalam this tidak dapat diubah, nilai selalu sama dengan this luar.

Contoh lain

Dengan fungsi panah, nilai this tidak dapat diubah dengan bind:

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

Dengan fungsi panah, nilai this tidak dapat diubah dengan call atau apply:

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

Dengan fungsi panah, nilai this tidak dapat diubah dengan memanggil fungsi sebagai anggota objek lain:

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

Dengan fungsi panah, nilai this tidak dapat diubah dengan memanggil fungsi sebagai konstruktor:

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

Metode instance 'Terikat'

Dengan metode instance, jika Anda ingin memastikan this selalu merujuk pada instance class, cara terbaiknya adalah menggunakan fungsi panah dan kolom class:

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

Pola ini sangat berguna saat menggunakan metode instance sebagai pemroses peristiwa dalam komponen (seperti komponen React, atau komponen web).

Hal di atas mungkin terasa melanggar aturan "this akan sama dengan this dalam cakupan induk", tetapi hal tersebut mulai masuk akal jika Anda menganggap kolom class sebagai sugar sintaksis untuk menetapkan berbagai hal dalam konstruktor:

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

Paten alternatif melibatkan binding fungsi yang ada di konstruktor, atau menetapkan fungsi di dalam konstruktor. Jika Anda tidak dapat menggunakan kolom class karena alasan tertentu, menetapkan fungsi di konstruktor adalah alternatif yang wajar:

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

Atau, jika fungsi/class dipanggil dengan new:

new Whatever();

Hal di atas akan memanggil Whatever (atau fungsi konstruktornya jika berupa class) dengan this yang ditetapkan ke hasil Object.create(Whatever.prototype).

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

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

Hal yang sama berlaku untuk konstruktor gaya lama:

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

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

Contoh lain

Saat dipanggil dengan new, nilai this tidak dapat diubah dengan bind:

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

Saat dipanggil dengan new, nilai this tidak dapat diubah dengan memanggil fungsi sebagai anggota objek lain:

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

Sebaliknya, jika fungsi memiliki nilai this 'terikat':

function someFunction() {
  return this;
}

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

Setiap kali boundFunction dipanggil, nilai this-nya akan menjadi objek yang diteruskan ke bind (boundObject).

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

Contoh lain

Saat memanggil fungsi terikat, nilai this tidak dapat diubah dengan call atau 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);

Saat memanggil fungsi terikat, nilai this tidak dapat diubah dengan memanggil fungsi tersebut sebagai anggota objek lain:

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

Atau, jika this disetel pada waktu panggilan:

function someFunction() {
  return this;
}

const someObject = {hello: 'world'};

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

Nilai this adalah objek yang diteruskan ke call/apply.

Sayangnya, this disetel ke beberapa nilai lain oleh hal-hal seperti pemroses peristiwa DOM, dan penggunaannya dapat menghasilkan kode yang sulit dipahami:

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

Saya menghindari penggunaan this dalam kasus seperti di atas, dan sebagai gantinya:

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

Atau, jika fungsi dipanggil melalui objek induk (parent.func()):

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

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

Dalam hal ini, fungsi tersebut dipanggil sebagai anggota obj sehingga this akan menjadi obj. Hal ini terjadi pada waktu panggilan, sehingga link akan rusak jika fungsi dipanggil tanpa objek induknya, atau dengan objek induk yang berbeda:

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 salah karena someMethod tidak dipanggil sebagai anggota obj. Anda mungkin menemukan kesalahan saat mencoba sesuatu seperti ini:

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

Hal ini rusak karena implementasi querySelector melihat nilai this-nya sendiri dan mengharapkannya sebagai jenis node DOM, dan implementasi di atas memutus koneksi tersebut. Untuk mencapai hal di atas dengan benar:

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

Fakta menarik: Tidak semua API menggunakan this secara internal. Metode konsol seperti console.log diubah untuk menghindari referensi this, sehingga log tidak perlu terikat ke console.

Sebaliknya, jika fungsi atau cakupan induk berada dalam mode ketat:

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

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

Dalam hal ini, nilai this tidak terdefinisi. 'use strict' tidak diperlukan dalam fungsi ini jika cakupan induk berada dalam mode ketat (dan semua modul dalam mode ketat).

Atau:

function someFunction() {
  return this;
}

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

Dalam hal ini, nilai this sama dengan globalThis.

Fiuh!

Dan selesai! Itu yang saya tahu tentang this. Ada pertanyaan? Sesuatu yang saya lewatkan? Jangan ragu untuk menyampaikan saya.

Terima kasih kepada Mathias Bynens, Ingvar Stepanyan, dan Thomas Steiner yang telah memberikan ulasan.