1 September 2021
Materi ini hanya tersedia dalam bahasa berikut: عربي, English, Español, فارسی, Français, Italiano, 日本語, 한국어, Русский, Українська, 简体中文. Tolong, bantu kami menerjemahkan ke dalam Indonesia.

Reference Type

In-depth language feature

This article covers an advanced topic, to understand certain edge-cases better.

It’s not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood.

A dynamically evaluated method call can lose this.

For instance:

let user = {
  name: "John",
  hi() { alert(this.name); },
  bye() { alert("Bye"); }
};

user.hi(); // works

// now let's call user.hi or user.bye depending on the name
(user.name == "John" ? user.hi : user.bye)(); // Error!

On the last line there is a conditional operator that chooses either user.hi or user.bye. In this case the result is user.hi.

Then the method is immediately called with parentheses (). But it doesn’t work correctly!

As you can see, the call results in an error, because the value of "this" inside the call becomes undefined.

This works (object dot method):

user.hi();

This doesn’t (evaluated method):

(user.name == "John" ? user.hi : user.bye)(); // Error!

Why? If we want to understand why it happens, let’s get under the hood of how obj.method() call works.

Reference type explained

Looking closely, we may notice two operations in obj.method() statement:

  1. First, the dot '.' retrieves the property obj.method.
  2. Then parentheses () execute it.

So, how does the information about this get passed from the first part to the second one?

If we put these operations on separate lines, then this will be lost for sure:

let user = {
  name: "John",
  hi() { alert(this.name); }
}

// split getting and calling the method in two lines
let hi = user.hi;
hi(); // Error, because this is undefined

Here hi = user.hi puts the function into the variable, and then on the last line it is completely standalone, and so there’s no this.

To make user.hi() calls work, JavaScript uses a trick – the dot '.' returns not a function, but a value of the special Reference Type.

The Reference Type is a “specification type”. We can’t explicitly use it, but it is used internally by the language.

The value of Reference Type is a three-value combination (base, name, strict), where:

  • base is the object.
  • name is the property name.
  • strict is true if use strict is in effect.

The result of a property access user.hi is not a function, but a value of Reference Type. For user.hi in strict mode it is:

// Reference Type value
(user, "hi", true)

When parentheses () are called on the Reference Type, they receive the full information about the object and its method, and can set the right this (=user in this case).

Reference type is a special “intermediary” internal type, with the purpose to pass information from dot . to calling parentheses ().

Any other operation like assignment hi = user.hi discards the reference type as a whole, takes the value of user.hi (a function) and passes it on. So any further operation “loses” this.

So, as the result, the value of this is only passed the right way if the function is called directly using a dot obj.method() or square brackets obj['method']() syntax (they do the same here). There are various ways to solve this problem such as func.bind().

Summary

Reference Type is an internal type of the language.

Reading a property, such as with dot . in obj.method() returns not exactly the property value, but a special “reference type” value that stores both the property value and the object it was taken from.

That’s for the subsequent method call () to get the object and set this to it.

For all other operations, the reference type automatically becomes the property value (a function in our case).

The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression.

Tugas

pentingnya: 2

Apa hasil dari kode berikut ini?

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)()

P.S. Ada jebakannya :)

Error!

Coba ini:

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)() // error!

Pesan error pada kebanyakan peramban tidak memberitahukan kita cukup petunjuk tentang hal apa yang salah.

Error muncul karena tidak adanya sebuah titik koma setelah user = {...}.

JavaScript tidak secara otomatis menyisipkan sebuah tanda titik koma setelah tanda kurung kurawa (user.go)(), jadi JavaScript membaca kode seperti ini:

let user = { go:... }(user.go)()

Lalu kita juga bisa melihat bahwa ekspresi gabungan semacam itu adalah sebuah panggilan objek { go: ... } secara sintaks yang juga sebagai sebuah fungsi dengan argumen (user.go). Dan hal tersebut juga terjadi pada baris yang sama dengan let user, jadi objek user belum didefinisikan, oleh karena itu terjadi error.

Jika kita menyisipkan tanda titik koma, semuanya akan baik-baik saja:

let user = {
  name: "John",
  go: function() { alert(this.name) }
};

(user.go)() // John

Tolong ingat bahwa tanda kurung kurawa yang merangkap (user.go) tidak melakukan apapun di sini. Biasanya Biasanya tanda kurung kurawa mengatur urutan operasi, tapi di sini tanda titik-lah (.) yang berjalan terlebih dulu, jadi tidak ada pengaruh apapun. Hanya tanda titik koma yang berpengaruh.

pentingnya: 3

Dalam kode di bawah ini kita bermaksud untuk memanggil metode obj.go() sebanyak 4 kali sekaligus.

Tapi panggilan (1) dan (2) bekerja berbeda dibanding dengan (3) dan (4). Mengapa demikian?

let obj, method;

obj = {
  go: function() { alert(this); }
};

obj.go();               // (1) [objek Object]

(obj.go)();             // (2) [objek Object]

(method = obj.go)();    // (3) undefined

(obj.go || obj.stop)(); // (4) undefined

Ini dia penjelasannya.

  1. Itu adalah sebuah panggilan metode objek biasa.

  2. Sama halnya, tanda kurung kurawa tidak merubah urutan operasi di sini, lagi pula tanda titik lah yang pertama di urutan operasi.

  3. Di sini kita memiliki sebuah panggilan yang lebih kompleks lagi yakni (expression).method(). Pagnggilan tersebut bekerja sebagaimana jika panggilan itu dipisah menjadi dua baris kode:

    f = obj.go; // mengkalkulasi ekspresi
    f();        // memanggil apa yang kita punya

    Di sini f() dieksekusi sebagai sebuah fungsi, tanpa this.

  4. Hal serupa pada panggilan (3), di sebelah kiri tanda titik . kita memiliki sebuah ekspresi.

Untuk menjelaskan perilaku panggilan (3) dan (4) kita perlu memanggil ulang, yang mana properti pengakses (tanda titik atau tanda kurung siku) mengembalikan sebuah nilai dari tipe referensi (Reference Type).

Operasi apapun kecuali sebuah panggilan metode (seperti penugasan = atau ||) membuat membuat ekspresi tersebut menjadi sebuah nilai biasa, yang tidak membawa informasi yang memungkinkan untuk menentukan this.

Peta tutorial

komentar

baca ini sebelum berkomentar…
  • Jika Anda memiliki saran apa yang harus ditingkatkan - silakan kunjungi kirimkan Github issue atau pull request sebagai gantinya berkomentar.
  • Jika Anda tidak dapat memahami sesuatu dalam artikel – harap jelaskan.
  • Untuk menyisipkan beberapa kata kode, gunakan tag <code>, untuk beberapa baris – bungkus dengan tag <pre>, untuk lebih dari 10 baris – gunakan sandbox (plnkr, jsbin, < a href='http://codepen.io'>codepen…)