Lampiran

Pewarisan prototipe

Dengan pengecualian null dan undefined, setiap jenis data primitif memiliki prototipe, wrapper objek yang sesuai yang menyediakan metode untuk menggunakan nilai. Saat pencarian metode atau properti dipanggil pada primitif, JavaScript menggabungkan primitif di balik layar dan memanggil metode atau melakukan pencarian properti pada objek wrapper.

Misalnya, literal string tidak memiliki metodenya sendiri, tetapi Anda dapat memanggil metode .toUpperCase() di dalamnya berkat wrapper objek String yang sesuai:

"this is a string literal".toUpperCase();
> THIS IS A STRING LITERAL

Hal ini disebut pewarisan prototipe—mewarisi properti dan metode dari konstruktor yang sesuai dengan nilai.

Number.prototype
> Number { 0 }
>  constructor: function Number()
>  toExponential: function toExponential()
>  toFixed: function toFixed()
>  toLocaleString: function toLocaleString()
>  toPrecision: function toPrecision()
>  toString: function toString()
>  valueOf: function valueOf()
>  <prototype>: Object {  }

Anda dapat membuat primitif menggunakan konstruktor ini, bukan hanya menentukannya berdasarkan nilainya. Misalnya, menggunakan konstruktor String akan membuat objek string, bukan literal string: objek yang tidak hanya berisi nilai string, tetapi semua properti dan metode yang diwarisi dari konstruktor.

const myString = new String( "I'm a string." );

myString;
> String { "I'm a string." }

typeof myString;
> "object"

myString.valueOf();
> "I'm a string."

Sebagian besar, objek yang dihasilkan berperilaku seperti nilai yang telah kita gunakan untuk menentukan objek tersebut. Misalnya, meskipun menentukan nilai angka menggunakan konstruktor new Number menghasilkan objek yang berisi semua metode dan properti prototipe Number, Anda dapat menggunakan operator matematika pada objek tersebut seperti yang Anda lakukan pada literal angka:

const numberOne = new Number(1);
const numberTwo = new Number(2);

numberOne;
> Number { 1 }

typeof numberOne;
> "object"

numberTwo;
> Number { 2 }

typeof numberTwo;
> "object"

numberOne + numberTwo;
> 3

Anda jarang sekali perlu menggunakan konstruktor ini, karena pewarisan prototipe bawaan JavaScript berarti konstruktor ini tidak memberikan manfaat praktis. Membuat primitif menggunakan konstruktor juga dapat menyebabkan hasil yang tidak terduga, karena hasilnya adalah objek, bukan literal sederhana:

let stringLiteral = "String literal."

typeof stringLiteral;
> "string"

let stringObject = new String( "String object." );

stringObject
> "object"

Hal ini dapat mempersulit penggunaan operator perbandingan ketat:

const myStringLiteral = "My string";
const myStringObject = new String( "My string" );

myStringLiteral === "My string";
> true

myStringObject === "My string";
> false

Penyisipan titik koma otomatis (ASI)

Saat mengurai skrip, penafsir JavaScript dapat menggunakan fitur yang disebut penyisipan titik koma otomatis (ASI) untuk mencoba memperbaiki instance titik koma yang dihilangkan. Jika menemukan token yang tidak diizinkan, parser JavaScript akan mencoba menambahkan titik koma sebelum token tersebut untuk memperbaiki potensi error sintaksis, selama satu atau beberapa kondisi berikut benar:

  • Token tersebut dipisahkan dari token sebelumnya dengan baris baru.
  • Token tersebut adalah }.
  • Token sebelumnya adalah ), dan titik koma yang disisipkan akan menjadi titik koma akhir dari pernyataan dowhile.

Untuk informasi selengkapnya, lihat aturan ASI.

Misalnya, menghilangkan titik koma setelah pernyataan berikut tidak akan menyebabkan error sintaksis karena ASI:

const myVariable = 2
myVariable + 3
> 5

Namun, ASI tidak dapat memperhitungkan beberapa pernyataan pada baris yang sama. Jika Anda menulis lebih dari satu pernyataan di baris yang sama, pastikan untuk memisahkannya dengan titik koma:

const myVariable = 2 myVariable + 3
> Uncaught SyntaxError: unexpected token: identifier

const myVariable = 2; myVariable + 3;
> 5

ASI adalah upaya untuk memperbaiki error, bukan jenis fleksibilitas sintaksis yang di-build ke dalam JavaScript. Pastikan untuk menggunakan titik koma jika perlu sehingga Anda tidak mengandalkannya untuk menghasilkan kode yang benar.

Mode ketat

Standar yang mengatur cara penulisan JavaScript telah berkembang jauh di luar hal yang dipertimbangkan selama desain awal bahasa. Setiap perubahan baru pada perilaku yang diharapkan JavaScript harus menghindari penyebab error di situs lama.

ES5 mengatasi beberapa masalah lama dengan semantik JavaScript tanpa mengganggu implementasi yang ada dengan memperkenalkan "mode ketat", cara untuk memilih kumpulan aturan bahasa yang lebih ketat untuk seluruh skrip atau setiap fungsi. Untuk mengaktifkan mode ketat, gunakan literal string "use strict", diikuti dengan titik koma, di baris pertama skrip atau fungsi:

"use strict";
function myFunction() {
  "use strict";
}

Mode ketat mencegah tindakan "tidak aman" tertentu atau fitur yang tidak digunakan lagi, menampilkan error eksplisit sebagai pengganti error "bisu" umum, dan melarang penggunaan sintaksis yang mungkin bertentangan dengan fitur bahasa mendatang. Misalnya, keputusan desain awal di sekitar cakupan variabel membuat developer lebih cenderung "mencemari" cakupan global secara tidak sengaja saat mendeklarasikan variabel, terlepas dari konteks yang berisi, dengan menghapus kata kunci var:

(function() {
  mySloppyGlobal = true;
}());

mySloppyGlobal;
> true

Runtime JavaScript modern tidak dapat memperbaiki perilaku ini tanpa risiko menghancurkan situs apa pun yang mengandalkannya, baik secara tidak sengaja maupun sengaja. Sebagai gantinya, JavaScript modern mencegahnya dengan mengizinkan developer memilih mode ketat untuk pekerjaan baru, dan mengaktifkan mode ketat secara default hanya dalam konteks fitur bahasa baru yang tidak akan merusak implementasi lama:

(function() {
    "use strict";
    mySloppyGlobal = true;
}());
> Uncaught ReferenceError: assignment to undeclared variable mySloppyGlobal

Anda harus menulis "use strict" sebagai literal string. Literal template (use strict) tidak akan berfungsi. Anda juga harus menyertakan "use strict" sebelum kode yang dapat dieksekusi dalam konteks yang diinginkan. Jika tidak, penafsir akan mengabaikannya.

(function() {
    "use strict";
    let myVariable = "String.";
    console.log( myVariable );
    sloppyGlobal = true;
}());
> "String."
> Uncaught ReferenceError: assignment to undeclared variable sloppyGlobal

(function() {
    let myVariable = "String.";
    "use strict";
    console.log( myVariable );
    sloppyGlobal = true;
}());
> "String." // Because there was code prior to "use strict", this variable still pollutes the global scope

Menurut referensi, menurut nilai

Setiap variabel, termasuk properti objek, parameter fungsi, dan elemen dalam array, set, atau peta, dapat berisi nilai primitif atau nilai referensi.

Saat nilai primitif ditetapkan dari satu variabel ke variabel lain, mesin JavaScript akan membuat salinan nilai tersebut dan menetapkannya ke variabel.

Saat Anda menetapkan objek (instance class, array, dan fungsi) ke variabel, variabel tersebut akan berisi referensi ke posisi objek yang disimpan dalam memori, bukan membuat salinan baru objek tersebut. Oleh karena itu, mengubah objek yang dirujuk oleh variabel akan mengubah objek yang dirujuk, bukan hanya nilai yang dimuat oleh variabel tersebut. Misalnya, jika Anda menginisialisasi variabel baru dengan variabel yang berisi referensi objek, lalu menggunakan variabel baru untuk menambahkan properti ke objek tersebut, properti dan nilainya akan ditambahkan ke objek asli:

const myObject = {};
const myObjectReference = myObject;

myObjectReference.myProperty = true;

myObject;
> Object { myProperty: true }

Hal ini penting tidak hanya untuk mengubah objek, tetapi juga untuk melakukan perbandingan ketat, karena kesetaraan ketat antara objek mengharuskan kedua variabel untuk mereferensikan objek yang sama untuk dievaluasi ke true. Keduanya tidak dapat mereferensikan objek yang berbeda, meskipun objek tersebut secara struktural identik:

const myObject = {};
const myReferencedObject = myObject;
const myNewObject = {};

myObject === myNewObject;
> false

myObject === myReferencedObject;
> true

Alokasi memori

JavaScript menggunakan pengelolaan memori otomatis, yang berarti memori tidak perlu dialokasikan atau didealokasikan secara eksplisit selama proses pengembangan. Meskipun detail pendekatan mesin JavaScript untuk pengelolaan memori berada di luar cakupan modul ini, memahami cara memori dialokasikan akan memberikan konteks yang berguna untuk menggunakan nilai referensi.

Ada dua "area" dalam memori: "stack" dan "heap". Stack menyimpan data statis—nilai primitif dan referensi ke objek—karena jumlah ruang tetap yang diperlukan untuk menyimpan data ini dapat dialokasikan sebelum skrip dieksekusi. Heap menyimpan objek, yang memerlukan ruang yang dialokasikan secara dinamis karena ukurannya dapat berubah selama eksekusi. Memori dibebaskan oleh proses yang disebut "pembersihan sampah memori", yang menghapus objek tanpa referensi dari memori.

Thread utama

JavaScript pada dasarnya adalah bahasa dengan thread tunggal dengan model eksekusi "sinkron", yang berarti hanya dapat mengeksekusi satu tugas dalam satu waktu. Konteks eksekusi berurutan ini disebut thread utama.

Thread utama digunakan bersama oleh tugas browser lainnya, seperti mengurai HTML, merender dan merender ulang bagian halaman, menjalankan animasi CSS, dan menangani interaksi pengguna mulai dari yang sederhana (seperti menandai teks) hingga yang kompleks (seperti berinteraksi dengan elemen formulir). Vendor browser telah menemukan cara untuk mengoptimalkan tugas yang dilakukan oleh thread utama, tetapi skrip yang lebih kompleks masih dapat menggunakan terlalu banyak resource thread utama dan memengaruhi performa halaman secara keseluruhan.

Beberapa tugas dapat dijalankan di thread latar belakang yang disebut Web Worker, dengan beberapa batasan:

  • Thread pekerja hanya dapat bertindak pada file JavaScript mandiri.
  • Akses ke jendela dan UI browser sangat berkurang atau tidak ada.
  • Cara berkomunikasi dengan thread utama terbatas.

Batasan ini membuatnya ideal untuk tugas yang berfokus dan membutuhkan banyak resource yang mungkin akan menempati thread utama.

Stack panggilan

Struktur data yang digunakan untuk mengelola "konteks eksekusi"—kode yang secara aktif dieksekusi—adalah daftar yang disebut stack panggilan (sering kali hanya "stack"). Saat skrip pertama kali dijalankan, penafsir JavaScript akan membuat "konteks eksekusi global" dan mendorongnya ke stack panggilan, dengan pernyataan di dalam konteks global tersebut dijalankan satu per satu, dari atas ke bawah. Saat penafsir menemukan panggilan fungsi saat mengeksekusi konteks global, penafsir akan mendorong "konteks eksekusi fungsi" untuk panggilan tersebut ke bagian atas stack, menjeda konteks eksekusi global, dan mengeksekusi konteks eksekusi fungsi.

Setiap kali fungsi dipanggil, konteks eksekusi fungsi untuk panggilan tersebut akan didorong ke bagian atas stack, tepat di atas konteks eksekusi saat ini. Stack panggilan beroperasi berdasarkan "terakhir masuk, pertama keluar", yang berarti bahwa panggilan fungsi terbaru, yang tertinggi dalam stack, dieksekusi dan berlanjut hingga selesai. Saat fungsi tersebut selesai, penafsir akan menghapusnya dari stack panggilan, dan konteks eksekusi yang berisi panggilan fungsi tersebut menjadi item tertinggi di stack lagi dan melanjutkan eksekusi.

Konteks eksekusi ini menangkap nilai apa pun yang diperlukan untuk eksekusinya. Fungsi ini juga menetapkan variabel dan fungsi yang tersedia dalam cakupan fungsi berdasarkan konteks induknya, serta menentukan dan menetapkan nilai kata kunci this dalam konteks fungsi.

Antrean callback dan loop peristiwa

Eksekusi berurutan ini berarti bahwa tugas asinkron yang menyertakan fungsi callback, seperti mengambil data dari server, merespons interaksi pengguna, atau menunggu timer yang ditetapkan dengan setTimeout atau setInterval, akan memblokir thread utama hingga tugas tersebut selesai, atau secara tidak terduga mengganggu konteks eksekusi saat ini saat konteks eksekusi fungsi callback ditambahkan ke stack. Untuk mengatasi hal ini, JavaScript mengelola tugas asinkron menggunakan "model konkurensi" berbasis peristiwa yang terdiri dari "loop peristiwa" dan "antrean callback" (terkadang disebut sebagai "antrean pesan").

Saat tugas asinkron dieksekusi di thread utama, konteks eksekusi fungsi callback ditempatkan di antrean callback, bukan di atas stack panggilan. Loop peristiwa adalah pola yang terkadang disebut reactor, yang terus-menerus mengambil status stack panggilan dan antrean callback. Jika ada tugas dalam antrean callback dan loop peristiwa menentukan bahwa stack panggilan kosong, tugas dari antrean callback akan didorong ke stack satu per satu untuk dieksekusi.