Javascript Fundamental — Scope : Part 3 [Bahasa Indonesia]

Tri Hargianto
JogjaJS Community
Published in
7 min readMay 31, 2020

Javascript Fundamental — Scope adalah series yang akan membahas scope di Javascript sedikit lebih mendalam. Saya membaginya dalam beberapa bagian dan saya menyarankan buat membaca series ini secara berurutan.“

  1. Bagian 1 : Konsep LHS & RHS look-up, proses Engine memproses variable, konsep Scope & Nested Scope.
  2. Bagian 2 : Lexical Scope & Shadowing Variable
  3. Bagian 3 : Function scope, Global Namespace, IIFE, dan Block Scope

Function Scope

Pada bagian 1 dan bagian 2, kita udah belajar ketika kita bikin sebuah function, maka sebuah gelembung baru (scope) akan terbentuk, yang artinya identifier (variabel/function) yang kita bikin di dalamnya ngga bisa diakses (tersembunyi) dari scope luarnya. Nah, scope ini disebut Function Scope.

Salah satu keuntungan function scope, yaitu kita bisa menghindari variabel dengan nama yang sama saling bertabrakan, atau dengan kata lain variabel yang kita bikin tidak akan menimpa variabel yang udah didefinisikan di tempat lain.

Contoh :

var text = "Belajar JS";function greeting() {
var text = "Hello!";

console.log(text);
}
greeting(); // Hello!console.log(text); // Belajar JS

Pada kode di atas, saya melakukan shadowing variable buat variabel text.

Namun, perlu diperhatikan saya menambahkan keyword var di depan variabel text buat mendefinisikan ulang variabel tersebut di dalam function greeting(), sehingga bisa terjadi shadowing variable.

Hal berbeda bisa terjadi pada kode di bawah :

var text = "Belajar JS";function greeting() {
text = "Hello!";

console.log(text);
}
greeting(); // Hello!console.log(text); // Hello!

Pada kode di atas, saya tidak menambahkan keyword var, sehingga meskipun di dalam function greeting(), ia akan menimpa variabel text di scope luarnya. Hal seperti ini sebisa mungkin dihindari, karena ketika kode aplikasi kita sudah ribuan baris, variabel di scope tertentu yang dipake di tempat lain di kode kita, tiba-tiba tanpa kita sadari nilainya tertimpa oleh function lain, yang mana bisa jadi bug di aplikasi kita.

Jadi saran saya selalu buat variabel dengan keyword var di dalam function supaya semua identifier (variabel, function) dari suatu function memang benar-benar tertutup, tersembunyi, dan tidak menimbulkan efek di scope lain.

Kenapa penting menyembunyikan semua identifier dan logic di dalam sebuah function? kenapa semua variabel tidak di letakkan di global scope saja? 🤔

Kamu pernah pake library bukan? seperti jquery, slider, react, dll.

Pernah gak terpikir ada berapa banyak variabel yang library-library tersebut pake di dalamnya, yang mungkin variabel-variabel di dalamnya banyak yang memakai nama umum seperti name, count, result, dll.

Setelah meng-inject library tersebut di aplikasi kita, kita masih bebas kan memakai variabel seperti name, count, result, dll tanpa mempengaruhi library tersebut bukan? Hal ini masih bisa kita lakukan karena beberapa library-library tersebut membungkus semua identifier dan logic nya di dalam function scope, kemudian hanya meng-ekspos yang penting-penting saja, selain itu semuanya tersembunyi di dalamnya.

Contoh :

function calculator() {

function convertInt(param){
return parseInt(param, 10);
}
function tambah(a, b) {
return convertInt(a) + convertInt(b);
}
function kurang(a, b) {
return convertInt(a) - convertInt(b);
}
return {
tambah: tambah,
kurang: kurang
}
}var myLib = calculator();// Output: 10
console.log(myLib.tambah(5, 5));
// Output: 3
console.log(myLib.kurang(5, 2));
// Error: TypeError!
console.log(myLib.convertInt(5));

BTW, Library kalkulator di atas pasti gak ada yang mau pake 🤣, dan berbelit-belit. tapi ini sebagai contoh saja.

Saya membungkus semua function di dalam function calculator yang mana function convertInt, tambah dan kurang hanya berada di dalam function calculator. Sehingga, scope luarnya pun masih bebas menggunakan function bernama tambah maupun kurang, karena saya telah melakukan shadowing identifier/variable di function ini.

Lalu saya mengekspos function tambah dan kurang saja, convertInt tetep anteng di dalam function calculator.

Function calculator hanya peduli dengan scope nya sendiri dan tidak membuat efek samping di scope luarnya.

Global Namespace

Selain function, beberapa library di luar sana banyak juga membungkus dan menyembunyikan berbagai logic dan identifier nya di satu variabel object di dalam global scope. Teknik ini bisa juga disebut Global Namespace.

Contoh:

var calculator = {
text: 'Hasilnya adalah :',
tambah: function(a, b) {
return this.text + " " + (a+b);
},
kurang: function(a, b) {
return this.text + " " + (a-b);
}
}
// Hasilnya adalah: 10
console.log(
calculator.tambah(5, 5)
);
// Hasilnya adalah: 5
console.log(
calculator.kurang(10, 5)
);

Kalo temen-temen perhatikan di library jQuery, ia menggunakan function scope dan global namespace di dalam kodenya.

Kelemahan style global namespace seperti ini, semua property di dalamnya, seperti calculator.text tetap bisa di akses dari luar lho ya. Jadi, gak ada private property.

Immediately Invoked Function Expression (IIFE)

Oke, sampai sini kita udah paham kalo kita mau membuat scope tertutup, tersembunyi dari scope luarnya, kita bisa manfaatin function.

Di kasus tertentu, terkadang kita perlu function yang kita buat langsung di eksekusi tepat setelah browser selesai memuat kode kita. Maka otomatis kita akan mendeklarasikan function lalu kita panggil langsung function tersebut.

Contoh :

var a = 2;// deklarasikan dulu functionnya
function init() {
var a = 3; console.log(a);
}
// lalu panggil disini
init();
console.log(a);

Walaupun tidak ada masalah, kode di atas terlalu berbelit-belit. Kita harus mendeklarasikan dulu function-nya trus baru bisa kita panggil setelahnya. Ditambah lagi, kita mengotori global scope dengan identifier bernama init() padahal tidak ada yang pake function itu lagi selain buat dieksekusi secara langsung.

Untuk itu, kita bisa pake teknik berikut :

var a = 2;(function() {

var a = 3;

console.log(a);
})();console.log(a);

Teknik di atas biasa disebut Immediately Invoked Function Expression atau disingkat IIFE, yang mana artinya tentu sesuai namanya ya “function yang langsung dieskekusi”.

Pada IIFE di atas, kita membuat kurung pertama (..) berisi function expression (function tanpa nama). Kemudian ditambahkan kurung kedua () supaya function tersebut langung dieksekusi.

Dengan IIFE kita juga bisa mengirim parameter ke dalamnya, seperti kode berikut :

var a = 2;(function(b) {  var a = 3 + b;

console.log(a); // 5
})(a);console.log(a); // 2

pada kode di atas, saya mengirimkan variabel a ke dalam IIFE dengan cara memasukkan variabel a ke dalam kurung kedua (a), lalu nilainya saya tampung ke dalam parameter bernama b ( (function(b) {.. ).

Dengan cara seperti ini, kita telah mengkopi nilai dari variabel a ke dalam parameter b. Kita juga menjaga IIFE tetap tertutup dan sekaligus juga terlihat jelas, mana variabel yang berasal dari luar.

Pola seperti bisa kita temukan di dalam library jQuery dan juga banyak library di luar sana.

(function($, global, document) {     /* pake $ untuk referensi-
* variabel jQuery,
* global untuk window
*/
}(jQuery, window, document));

Block sebagai Scope

Kalo kamu ngikutin dari Bagian 1 dan Bagian 2 series ini, kita sama-sama tau kalo mau bikin scope baru, kita harus bikin function.

Function scope merupakan scope yang paling umum digunakan di Javascript dan tentu saja merupakan pendekatan yang sudah umum digunakan selama puluhan tahun. Namun, apakah kita cuma bisa pake function aja buat bikin scope?

Banyak bahasa pemrograman selain Javascript mendukung Block Scope (scope di dalam kurung kurawal {...} ).

Jika kamu tidak pernah menulis kode dengan Block Scope, kamu pasti familiar dengan kode yang sangat umum berikut :

for ( var i = 0; i < 5; i++ ) {
console.log(i);
}

Melihat kode di atas, akan sangat menggoda untuk berpikir bahwa kita tidak bisa mengakses variabel i di luar for loop. Kenyataannya variabel i adalah variabel yang hidup di scope yang membungkus for loop, entah itu function scope atau global scope.

for (var i = 0; i < 5; i++) {
console.log(i);
}
/* variabel "i" masih bisa-
* diakses di luar for loop 😳
*/
console.log(i); // 5

Lagi, variabel i masih bisa di akses di luar for loop karena variabel tersebut hidup di function scope/global scope. BUKAN block scope.

Contoh lagi, perhatikan kode di bawah ini:

var foo = true;if (foo) {
var bar = 2;
}
console.log(bar); // 2

Kita masih bisa mengakses variabel bar di luar statement if karena variabel di atas bersifat function scope/global scope. BUKAN block scope.

Pertanyaannya, apakah kita bisa membuat block scope di Javascript? Jawabannya bisa 🎉.

try… catch

Tanpa kita semua sadari, faktanya deklarasi variabel di dalam klausa catch di dalam statement try/catch akan menjadi block scope.

Sebagai contoh :

try { // kode yang bikin error...} catch (err) { /* variabel "err" bisa-
* diakses di sini
*/
console.log(err);
}// Error: Reference Error!
console.log(err);

Yap, variabel err hanya ada di dalam klausa catch, yang berarti di sini kita udah punya scope selain function scope/global scope di Javascript yaitu block scope. Jadi kalo kita mau bikin variabel block scope di Javascript bisa pakai alternatif seperti berikut :

try { throw 5; } catch (nilai) {
console.log(nilai); // 5
}

Pada kode di atas, saya bikin variabel bernama nilai yang cuma bisa di akses di block scope catch.

Sampai sini mungkin kamu mikir, apaan tuh. ribet amat.

Ya, begini cara kita bikin block scope di Javascript, setidaknya sebelum ES6 — versi Javascript yang muncul tahun 2015 — muncul, karena pada awalnya Javascript memang bersifat function scope.

let

Sejauh ini kita telah melihat Javascript memiliki keterbatasan dalam membuat block scope, namun kita bisa mengakalinya menggunakan try/catch. Jika memang kita cuma punya opsi itu buat bikin block scope, maka memaksakan block scope di Javascript akan sangat merepotkan.

Untungnya, ES6 datang membawa solusi, dan mengenalkan keyword baru yaitu let yang mana cara baru untuk mendeklarasikan variabel selain var.

Variabel yang dibuat menggunakan keyword let akan hidup di dalam block scope (umumnya di antara { ... }) .

Sekarang kita coba buat variabel lagi tapi kali ini pake let.

let foo = true;if (foo) {
let bar = 2;

// Output: 2
console.log(bar);
}
// Error: ReferenceError!
console.log(bar);

Pada kode di atas, kita membuat variabel bar menggunakan keyword let, di dalam if statement. Karena kita biasa menambahkan {...} di dalam if statement, maka secara implisit Javascript membuatkan kita block scope di blok tersebut.

Jika mau, kita bisa membuat block scope secara eksplisit.

let foo = true;if (foo) {
{ // <- eksplisit block
let bar = 2;
console.log(bar); // 2
} // <- tutup nya 😁
}
// Error: ReferenceError!
console.log(bar);

Banyak developer lebih memilih menuliskan block scope secara eksplisit karena lebih jelas dan lebih mudah dibaca dimana scope kita hidup.

const

Selain let, ES6 juga memperkenalkan const, yang bisa digunakan juga buat membuat variabel di dalam block scope, bedanya dengan let yaitu nilainya gak bisa diubah sekalinya udah didefinisikan.

var foo = true;if (foo) {
var a = 1;
let b = 2;
const c = 3;
a = 4; // diizinkan
b = 5; // diizinkan
c = 6; // gak boleh. Error!
}
// Output: 1
console.log(a);
// Reference Error!
console.log(b);
// Reference Error!
console.log(c);

There is no new things under the sun. Tulisan ini diadaptasi dan terinpirasi dari buku Kyle Simpson berjudul You Don’t Know JS: Scope & Closures https://www.amazon.com/gp/product/1449335586

✍ Jika ada penulisan yang salah atau pertanyaan silahkan kirim respon/komentar

--

--