Pewarisan prototipe
Dengan pengecualian null
dan undefined
, setiap jenis data primitif memiliki
prototype, wrapper objek terkait yang menyediakan metode untuk bekerja
dengan nilai-nilai. Saat pencarian metode atau properti dipanggil pada primitif,
JavaScript membungkus primitif di belakang layar dan memanggil metode atau
melakukan pencarian properti
pada objek wrapper.
Misalnya, literal string tidak memiliki metode sendiri, tetapi Anda dapat memanggil metode
.toUpperCase()
berkat objek String
yang sesuai
pembungkus:
"this is a string literal".toUpperCase();
> THIS IS A STRING LITERAL
Ini disebut pewarisan prototipe—mewarisi properti dan metode dari konstruktor nilai yang sesuai.
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 menentukan
mereka berdasarkan nilainya. Misalnya, menggunakan konstruktor String
akan membuat
objek string, bukan literal string: objek yang tidak hanya berisi string kita
tapi 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."
Secara umum, objek yang dihasilkan berperilaku sebagai nilai yang telah kita gunakan
mendefinisikannya. Misalnya, meskipun menentukan nilai angka menggunakan metode
Konstruktor new Number
menghasilkan objek yang berisi semua metode dan
properti prototipe Number
, Anda dapat menggunakan operator matematika
objek tersebut sama 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 akan sangat jarang menggunakan konstruktor ini, karena JavaScript bawaan pewarisan prototipe berarti mereka tidak memberikan manfaat praktis. Membuat primitif yang menggunakan konstruktor juga dapat menyebabkan hasil yang tidak diharapkan, 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 yang 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 yang dihilangkan titik koma. Jika parser JavaScript menemukan token yang tidak diizinkan, mencoba menambahkan titik koma sebelum token itu untuk memperbaiki selama satu atau beberapa kondisi berikut terpenuhi:
- Token tersebut dipisahkan dari token sebelumnya dengan baris baru.
- Token tersebut adalah
}
. - Token sebelumnya adalah
)
, dan titik koma yang disisipkan akan menjadi akhiran titik koma dari pernyataando
...while
.
Untuk mengetahui informasi selengkapnya, baca aturan ASI.
Misalnya, menghilangkan titik koma setelah pernyataan berikut tidak akan menyebabkan kesalahan sintaksis karena ASI:
const myVariable = 2
myVariable + 3
> 5
Namun, ASI tidak dapat memperhitungkan beberapa pernyataan di baris yang sama. Jika Anda menulis lebih dari satu pernyataan pada 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 {i>error<i}, bukan jenis fleksibilitas sintaksis yang ke dalam JavaScript. Pastikan untuk menggunakan titik koma jika sesuai sehingga Anda tidak mengandalkan untuk menghasilkan kode yang benar.
Mode ketat
Standar yang mengatur bagaimana JavaScript ditulis telah berkembang jauh melampaui hal apa pun yang dipertimbangkan selama desain awal bahasa. Setiap perubahan baru terhadap Perilaku JavaScript yang diharapkan harus mencegah terjadinya error di situs lama.
ES5 mengatasi beberapa masalah lama terkait semantik JavaScript tanpa
menghentikan implementasi yang ada dengan
memperkenalkan "mode ketat", cara memilih
menjadi seperangkat aturan bahasa yang lebih ketat
untuk seluruh skrip atau
fungsi individu. Untuk mengaktifkan mode ketat, gunakan literal string
"use strict"
, diikuti titik koma, pada baris pertama skrip atau
{i>function<i}:
"use strict";
function myFunction() {
"use strict";
}
Mode ketat mencegah "tidak aman" tertentu atau fitur yang tidak digunakan lagi, menampilkan
kesalahan eksplisit sebagai pengganti kata "diam" biasa , dan melarang penggunaan
sintaks yang mungkin bertabrakan dengan fitur bahasa mendatang. Misalnya, di awal
keputusan desain terkait cakupan variabel
membuat developer lebih mungkin melakukan "polling" secara keliru ruang lingkup global ketika
mendeklarasikan variabel, terlepas dari konteks yang memuatnya, dengan menghilangkan
Kata kunci var
:
(function() {
mySloppyGlobal = true;
}());
mySloppyGlobal;
> true
Runtime JavaScript modern tidak dapat memperbaiki perilaku ini tanpa risiko merusak situs web apa pun yang bergantung pada {i>cloud<i}, baik secara tidak sengaja atau sengaja. Sebaliknya, JavaScript modern mencegahnya dengan membiarkan pengembang untuk tugas baru, dan mengaktifkan mode ketat secara default hanya dalam konteks fitur bahasa baru yang tidak akan melanggar penerapan lama:
(function() {
"use strict";
mySloppyGlobal = true;
}());
> Uncaught ReferenceError: assignment to undeclared variable mySloppyGlobal
Anda harus menuliskan "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 dimaksudkan. 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
Berdasarkan referensi, berdasarkan nilai
Variabel apa pun, termasuk properti objek, parameter fungsi, dan elemen dalam array, set, atau peta, dapat berisi elemen primitif atau nilai referensi.
Saat nilai primitif ditetapkan dari satu variabel ke variabel lain, JavaScript membuat salinan dari nilai itu dan menetapkannya ke variabel.
Saat Anda menetapkan objek (instance class, array, dan fungsi) ke , alih-alih membuat salinan baru dari objek tersebut, variabel akan berisi referensi ke posisi objek yang disimpan dalam memori. Karena itu, mengubah objek yang direferensikan oleh variabel mengubah objek yang direferensikan, bukan hanya nilai yang terkandung oleh variabel tersebut. Misalnya, jika Anda melakukan inisialisasi variabel dengan variabel yang berisi referensi objek, lalu gunakan variabel untuk menambahkan properti ke objek tersebut, properti dan nilainya 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 data, karena persamaan yang ketat antar objek
mengharuskan kedua variabel untuk
merujuk objek yang sama untuk dievaluasi ke true
. Mereka tidak bisa
mereferensikan
objek yang berbeda, meskipun objek tersebut identik secara struktural:
const myObject = {};
const myReferencedObject = myObject;
const myNewObject = {};
myObject === myNewObject;
> false
myObject === myReferencedObject;
> true
Alokasi memori
JavaScript menggunakan manajemen memori otomatis, yang berarti bahwa memori tidak perlu dialokasikan atau didealokasikan secara eksplisit selama pengembangannya. Meskipun detail mesin JavaScript manajemen memori lebih dari sekadar ruang lingkup modul ini, memahami bagaimana memori dialokasikan memberikan manfaat konteks untuk bekerja dengan nilai referensi.
Ada dua "area" dalam memori: "stack" dan "heap". Stack menyimpan data statis—nilai primitif dan referensi ke objek—karena ruang tetap yang diperlukan untuk menyimpan data ini dapat dialokasikan sebelum skrip dijalankan. Heap menyimpan objek, yang memerlukan ruang yang dialokasikan secara dinamis karena ukurannya dapat berubah selama eksekusi. Memori dibebaskan oleh suatu proses yang disebut “pembersihan sampah memori,” yang menghapus objek tanpa referensi dari memori.
Thread utama
JavaScript pada dasarnya adalah bahasa thread tunggal dengan model "sinkron" model eksekusi, yang berarti hanya dapat mengeksekusi satu tugas pada satu waktu. Konteks eksekusi berurutan ini disebut thread utama.
Thread utama digunakan bersama oleh tugas browser lain, seperti mengurai HTML, merender dan merender ulang bagian halaman, menjalankan animasi CSS, dan menangani interaksi pengguna mulai dari yang sederhana (seperti menyoroti teks) hingga yang kompleks (seperti berinteraksi dengan elemen formulir). Vendor browser telah menemukan cara untuk mengoptimalkan tugas yang dilakukan oleh thread utama, tetapi lebih kompleks skrip masih dapat menggunakan terlalu banyak sumber daya thread utama dan berdampak secara keseluruhan performa halaman.
Beberapa tugas dapat dieksekusi dalam thread latar belakang yang disebut Web Workers, dengan beberapa batasan:
- Thread pekerja hanya dapat bertindak pada file JavaScript mandiri.
- Akses ke jendela dan UI browser telah dikurangi secara drastis atau bahkan tidak ada sama sekali.
- Cara berkomunikasi dengan thread utama memiliki keterbatasan.
Keterbatasan ini membuatnya ideal untuk tugas-tugas terfokus dan intensif sumber daya yang mungkin menempati thread utama.
Stack panggilan
Struktur data yang digunakan untuk mengelola "konteks eksekusi"—kode yang dijalankan secara aktif—adalah daftar yang disebut stack panggilan (sering kali hanya "tumpukan"). Saat skrip pertama kali dieksekusi, penafsir JavaScript membuat "konteks eksekusi global" dan mendorongnya ke stack panggilan, dengan pernyataan dalam konteks global tersebut dieksekusi satu per satu, dari atas hingga ke bawah. Jika penafsir menemukan panggilan fungsi saat mengeksekusi konteks global, ini mendorong "konteks eksekusi fungsi" untuk panggilan tersebut ke teratas stack, menjeda konteks eksekusi global, dan mengeksekusi fungsi dalam konteks eksekusi.
Setiap kali sebuah fungsi dipanggil, konteks eksekusi fungsi untuk panggilan tersebut akan didorong ke bagian atas tumpukan, tepat di atas konteks eksekusi saat ini. Stack panggilan beroperasi pada metode "pertama masuk, pertama keluar" berarti bahwa yang paling panggilan fungsi terbaru, yang berada pada posisi tertinggi dalam stack, dijalankan dan dilanjutkan hingga teratasi. Setelah fungsi tersebut selesai, penafsir akan menghapusnya dari stack panggilan, dan konteks eksekusi yang berisi panggilan fungsi tersebut menjadi item tertinggi dalam tumpukan lagi dan melanjutkan eksekusi.
Konteks eksekusi ini merekam nilai apa pun yang diperlukan untuk eksekusinya. Mereka
menetapkan variabel dan fungsi yang tersedia dalam ruang lingkup
fungsi berdasarkan konteks induknya, dan
menentukan serta menetapkan nilai
Kata kunci this
dalam konteks fungsi.
Loop peristiwa dan antrean callback
Eksekusi berurutan ini berarti bahwa tugas asinkron yang menyertakan callback
fungsi-fungsi lain, seperti mengambil data dari server, merespons interaksi pengguna,
atau menunggu timer yang disetel dengan setTimeout
atau setInterval
, akan memblokir
thread utama sampai tugas tersebut selesai, atau tiba-tiba mengganggu
konteks eksekusi saat ini pada saat konteks eksekusi fungsi callback
ditambahkan ke stack. Untuk mengatasinya, JavaScript mengelola tugas asinkron
menggunakan "model konkurensi" berbasis peristiwa terdiri dari "loop peristiwa" dan
"antrean callback" (terkadang disebut sebagai "antrean pesan").
Ketika tugas asinkron dieksekusi pada thread utama, callback konteks eksekusi fungsi ditempatkan di antrean callback, bukan di atas stack panggilan. {i>Loop <i}peristiwa adalah pola yang terkadang disebut reactor, yang secara berkelanjutan memeriksa status tumpukan panggilan dan antrean callback. Jika ada tugas di antrean callback dan loop peristiwa menentukan bahwa tumpukan panggilan kosong, tugas dari antrean callback didorong ke tumpukan satu per satu agar telah dijalankan.