Bekerja dengan Ketiadaan di JavaScript: Variabel dan Fungsi

Bekerja dengan Ketiadaan di JavaScript: Variabel dan Fungsi

Seperti banyak bahasa pemrograman lain, JavaScript mengenal indikator ketiadaan, yaitu nilai null dan undefined. Baik null maupun undefined adalah nilai eksklusif dari tipe data masing-masing, seperti yang pernah kita bahas di Berbicara Null, Undefined, Not Defined, dan Undeclared. Mudahnya, undefined adalah nilai yang dihasilkan sistem, sementara null adalah nilai yang disematkan secara sadar oleh pemrogram.

Meskipun pada dasarnya berbeda, null dan undefined memiliki banyak kemiripan. Keduanya sama-sama mengindikasikan ketiadaan; keduanya sama-sama nilai eksklusif dari tipe datanya masing-masing; dan keduanya sama-sama bersifat falsy. Karena kemiripan-kemiripan tersebut, kedua nilai tersebut kemudian diklasifikasikan sebagai nullish values.

Terdapat beberapa hal yang sebaiknya diperhatikan ketika bekerja dengan nilai-nilai nullish.

Nilai Nullish dan Variabel

Ketika sebuah variabel dideklarasikan tanpa nilai, maka nilainya adalah undefined. Meskipun pada umumnya tidak masalah, namun akan lebih baik jika pemrogram mendeklarasikan variabel dengan nilai null jika memang variabel sengaja dideklarasikan tanpa nilai.

let sesuatu; // hindari

let something = null; // utamakan

Selain karena keterbacaannya lebih baik, perilaku type coercion mereka juga berbeda. Ketika melakukan operasi yang memiliki ekspektasi evaluasi nilai bertipe number, undefined akan di-coerce menjadi NaN, sementara null menjadi 0.

let x;
let y = null;

console.log(1 + x); // NaN
console.log(1 + y); // 1

Kemudian perlu perhatikan juga, selagi variabel yang dideklarasikan tanpa nilai akan memiliki nilai undefined, variabel yang tidak pernah dideklarasikan... well, tidak eksis sama sekali.

Jika Anda melakukan pengecekan sederhana dengan if(foo) yang bergantung pada coercion undefined menjadi false, Anda akan menemukan ReferenceError jika foo belum dideklarasikan. Dalam keadaan Anda tidak yakin apakah variabel yang akan dicek sudah dideklarasikan, operator typeof bisa digunakan.

Operator typeof selalu mengembalikan nilai string, bahkan untuk variabel yang belum dideklarasikan, di mana dalam kasus tersebut ia akan mengembalikan "undefined".

if (foo) {
  // hindari!
  /* selain akan melempar ReferenceError jika foo
  ** belum dideklarasikan, pengecekan ini tidak meloloskan
  ** nilai-nilai falsy seperti 0, '', null
  */
}

if (typeof foo !== 'undefined') {
  // utamakan!
  /* hanya tidak meloloskan dua kasus: foo bernilai
  ** undefined, atau foo belum dideklarasikan
  */
}

Nilai Nullish dan Fungsi

Fungsi memperlakukan null dan undefined secara berbeda. Jika Anda memiliki sebuah fungsi yang menerima suatu parameter, memanggilnya tanpa memberikan argumen untuk parameter tersebut akan membuat nilai variabel parameter tersebut di dalam fungsi menjadi undefined.

function foo(val) {
  console.log(val);
}

foo(); // undefined

Jika kita perhatikan, variabel parameter selalu terdeklarasikan di dalam fungsi. Anda tidak akan mengalami ReferenceError jika Anda melakukan pengecekan if (val) di dalam fungsi tersebut. Namun, pilihan paling akurat untuk pengecekan undefined tetap dengan menggunakan typeof val === 'undefined'.

Bagaimana jika parameter fungsi kita memiliki nilai default? Ini adalah salah satu gotcha yang pernah membuat saya tersandung. Ternyata, sebagaimanapun miripnya null dan undefined, mereka tetap hal yang berbeda, mari kita simak.

function foo(val = 42) {
  return val;
}

foo(); // 42
foo(undefined); // 42
foo(null); // null

Memberikan nilai null tidak akan membuat parameter fungsi diberikan nilai default, tidak seperti undefined. Mengapa begitu? Sederhananya kembali ke definisi null di atas. Nilai null adalah nilai yang diberikan pemrogram untuk mengindikasikan ketiadaan nilai. Jadi, keberadaan nilai null dianggap intentional dan tidak memerlukan penggantian dengan nilai default.

Bicara soal intentional, bukankah ketika kita mengirimkan argumen undefined, itu juga dilakukan dengan sengaja? Kalau tidak, mengapa kita repot-repot melakukan foo(undefined) padahal foo() saja cukup?

Jawabannya, bisa saja keberadaan undefined sebagai argumen terjadi bukan secara sengaja dengan mengirimkan nilai primitif undefined. Contohnya seperti di bawah ini.

const book = {
  title: 'Moby Dick',
  author: 'Herman Melville',
  price: 100000
};

function calculateFinalPrice(basePrice, discount = 0) {
  return basePrice * (discount / 100);
}

function addToCart(book) {
  const finalPrice = calculateFinalPrice(book.price, book.discount);
  // ...
}

Pemanggilan calculateFinalPrice di dalam fungsi addToCart memang secara sengaja mengirimkan argumen kedua book.discount, yang mengarah ke parameter discount pada calculateFinalPrice. Namun, objek book ternyata tidak memiliki properti discount sehingga nilainya menjadi undefined. Dalam kasus ini, tentu masuk akal jika nilai default mengambilalih argumen book.discount yang dikirimkan, bukan?

Oke, berarti memanggil fungsi tanpa argumen sama saja dengan memanggilnya dengan argumen undefined? Tidak juga :). Memanggil fungsi dengan argumen undefined secara eksplisit dapat dibedakan dari memanggil fungsi tanpa argumen apapun.

Setiap fungsi dalam JavaScript memiliki objek arguments, sebuah objek Array-like berisi argumen-argumen yang diberikan kepada fungsi tersebut. Disebut Array-like karena memiliki kemiripan dengan Array dalam hal memiliki length, tetapi tidak memiliki semua properti Array. Melalui properti arguments.length tersebut kita bisa mengidentifikasi perbedaan pemanggilan foo() dan foo(undefined).

function foo() {
  console.log(arguments.length);
}

foo(); // 0
foo(undefined); // 1
foo(undefined, undefined, undefined); // 3

Kenapa perbedaan itu perlu diperhatikan? Mari kita ilustrasikan. Sejak ES6, kita bisa menggunakan rest parameters untuk merepresentasikan argumen-argumen tanpa batas jumlah yang dikirimkan ke fungsi. Dengan menggunakan rest parameters, argumen-argumen tersebut praktis menjadi sebuah Array dengan berbagai metode prototipenya.

function printArgs(...args) {
  args.forEach(arg => {
    console.log(arg);
  })
}

printArgs(); // loop tidak berjalan sama sekali
printArgs(undefined, undefined, undefined); // loop berjalan 3 kali

Pengetahuan tersebut mungkin akan berguna jika Anda membuat fungsi dengan panjang parameter yang dinamis, dan argumen yang dikirimkan kepadanya dapat memiliki nilai undefined.


Demikian ulasan tentang nullish values, null dan undefined, dalam kaitannya dengan variabel dan fungsi di JavaScript. Dari bahasan di atas, sepertinya lebih banyak perbedaannya, ya? Lalu kenapa null dan undefined dikelompokkan menjadi satu jika memiliki banyak perbedaan?

Jawabannya akan terlihat pada bahasan kita selanjutnya, Bekerja dengan Ketiadaan dalam Javascript: Objek dan Operator Tanda Tanya.


Referensi dan Bacaan Lanjutan