JavaScript: Apa artinya?

Mencari tahu nilai this bisa jadi rumit di JavaScript. Berikut cara melakukannya…

Jake Archibald
Jake Archibald

this JavaScript menjadi bahan lelucon, dan itu karena, yah, cukup rumit. Namun, saya telah melihat developer melakukan hal-hal yang jauh lebih rumit dan khusus domain untuk menghindari menangani this ini. Jika Anda tidak yakin dengan this, semoga informasi ini dapat membantu. Ini adalah panduan this saya.

Saya akan memulai dengan situasi yang paling spesifik, dan mengakhiri dengan situasi yang paling tidak spesifik. Artikel ini seperti if (…) … else if () … else if (…) … besar, sehingga Anda dapat langsung membuka bagian pertama yang cocok dengan kode yang 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 ditetapkan pada waktu panggilan
  5. Atau, jika fungsi dipanggil melalui objek induk (parent.func())
  6. Atau, jika fungsi atau cakupan induk 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, dan selalu sama dengan this bagian luar.

Contoh lainnya

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 'Bound'

Dengan metode instance, jika Anda ingin memastikan this selalu merujuk ke instance class, cara terbaik 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 seperti melanggar aturan "this akan sama dengan this dalam cakupan induk", tetapi hal ini mulai masuk akal jika Anda menganggap kolom class sebagai sintaksis yang mudah untuk menetapkan hal-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);
    };
  }
}

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

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

Jika tidak, jika fungsi/class dipanggil dengan new:

new Whatever();

Kode di atas akan memanggil Whatever (atau fungsi konstruktornya jika berupa class) dengan this 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 yang lebih lama:

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

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

Contoh lainnya

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

Jika tidak, 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 lainnya

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 sebagai anggota objek lain:

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

Jika tidak, jika this ditetapkan 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 ditetapkan ke beberapa nilai lain oleh hal-hal seperti pemroses peristiwa DOM, dan menggunakannya 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);
});

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

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

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

Dalam hal ini, fungsi 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 bernilai salah karena someMethod tidak dipanggil sebagai anggota obj. Anda mungkin telah mengalami masalah ini saat mencoba sesuatu seperti ini:

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

Hal ini rusak karena penerapan querySelector melihat nilai this-nya sendiri dan mengharapkannya menjadi semacam node DOM, dan hal di atas akan memutuskan 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 dengan console.

Jika tidak, 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 ditentukan. 'use strict' tidak diperlukan dalam fungsi jika cakupan induk berada dalam mode ketat (dan semua modul berada dalam mode ketat).

Atau:

function someFunction() {
  return this;
}

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

Dalam hal ini, nilai this sama dengan globalThis.

Fiuh!

Selesai. Itu semua yang saya tahu tentang this. Ada pertanyaan? Ada yang saya lewatkan? Jangan ragu untuk mengirim tweet kepada saya.

Terima kasih kepada Mathias Bynens, Ingvar Stepanyan, dan Thomas Steiner atas tinjauan ini.