Fungsi pada Javascript
Fungsi
adalah salah satu bagian yang paling indah dari Javascript. Sebagai
bahasa fungsional Javascript mengimplementasikan fungsi kelas pertama (first class function).
Fungsi dapat disimpan dalam variabel, dikembalikan oleh fungsi lain,
dan dikirimkan sebagai argumen untuk fungsi lainnya. Implementasi fungsi
yang sangat fleksibel seperti ini membuka banyak kesempatan kepada
pengembang untuk menuliskan kode yang bukan hanya berjalan dengan baik,
tetapi juga sangat elegan dan indah.
Sebuah
fungsi membungkus satu atau banyak perintah. Setiap kali kita memanggil
fungsi, maka perintah-perintah yang ada di dalam fungsi tersebut
dijalankan. Secara umum fungsi digunakan untuk penggunaan kembali kode (code reuse) dan penyimpanan informasi (information hiding).
Implementasi fungsi kelas pertama juga memungkinkan kita menggunakan
fungsi sebagai unit-unit yang dapat dikombinasikan, seperti layaknya
sebuah lego. Dukungan terhadap pemrograman berorientasi objek juga
berarti fungsi dapat kita gunakan untuk memberikan perilaku tertentu
dari sebuah objek.
Dalam
sudut pandang tertentu, kita bahkan dapat mengatakan bahwa intisari
dari pemrograman adalah mengubah atau menguraikan kebutuhan pengguna
menjadi fungsi dan struktur data. Oke, cukup untuk berbicara tentang
cinta penulis terhadap fungsi. Sekarang mari kita lihat langsungkenapa dan apakah fungsi benar-benar seperti yang diceritakan oleh penulis.
Pembuatan Fungsi pada Javascript
Sebuah fungsi pada Javascript dibuat dengan cara seperti berikut:
1 2 3 4 | function tambah(a, b) {
hasil = a + b;
return hasil;
}
|
Cara penulisan fungsi seperti ini dikenal dengan nama function declaration, atau deklarasi fungsi. Terdapat empat komponen yang membangun fungsi yang baru kita definisikan di atas, yaitu:
- Kata kunci
function
, yang memberitahu Javascript bahwa kita akan membuat fungsi. - Nama fungsi, dalam contoh di atas adalah
tambah
. Dengan memberikan sebuah fungsi nama maka kita dapat merujuk ke fungsi tersebut dengan nama yang diberikan. Harus diingat bawa nama fungsi bersifat opsional, yang berarti fungsi pada Javascript tidak harus diberi nama. Kita akan membahas tentang hal ini lebih dalam nanti. - Daftar parameter fungsi, yaitu
a, b
pada contoh di atas. Daftar parameter ini selalu dikelilingi oleh tanda kurung (()
). Parameter boleh kosong, tetapi tanda kurung wajib tetap dituliskan. Parameter fungsi akan secara otomatis didefinisikan menjadi variabel yang hanya bisa dipakai di dalam fungsi. Variabel pada parameter ini diisi dengan nilai yang dikirimkan kepada fungsi secara otomatis. - Sekumpulan perintah yang ada di dalam kurung kurawal (
{}
). Perintah-perintah ini dikenal dengan nama badan fungsi. Badan fungsi dieksekusi secara berurut ketika fungsi dijalankan.
Penulisan deklarasi fungsi (function declaration)
seperti di atas merupakan cara penulisan fungsi yang umumnya kita
gunakan pada bahasa pemrograman imperatif dan berorientasi objek. Tetapi
selain deklarasi fungsi Javascript juga mendukung cara penulisan fungsi
lain, yaitu dengan memanfaatkan ekspresi fungsi (function expression).
Ekspresi fungsi merupakan cara pembuatan fungsi yang memperbolehkan
kita melewatkan nama fungsi. Fungsi yang dibuat tanpa nama dikenal
dengan sebutan fungsi anonim atau fungsi lambda. Berikut adalah cara
membuat fungsi dengan ekspresi fungsi:
1 2 3 4 | var tambah = function (a, b) {
hasil = a + b;
return hasil;
};
|
Terdapat hanya sedikit perbedaan antara ekspresi fungsi dan deklarasi fungsi:
- Penamaan fungsi. Pada deklarasi fungsi, kita langsung memberikan nama fungsi sesuai dengan sintaks yang disediakan Javascript. Menggunakan ekspresi fungsi kita pada dasarnya menyimpan sebuah fungsi anonim ke dalam variabel, dan nama fungsi adalah nama variabel yang kita buat. Perlu diingat juga bahwa pada dasarnya ekspresi fungsi adalah fungsi anonim. Penyimpanan ke dalam variabel hanya diperlukan karena kita akan memanggil fungsi nantinya.
- Ekspresi fungsi dapat dipandang
sebagai sebuah ekspresi atau perintah standar bagi Javascript, sama
seperti ketika kita menuliskan kode
var i = 0;
. Deklarasi fungsi merupakan konstruksi khusus untuk membuat fungsi. Hal ini berarti pada akhir dari ekspresi fungsi kita harus menambahkan;
, sementara pada deklarasi fungsi hal tersbut tidak penting.
Karena
pada Javascript sebuah fungsi juga adalah sekaligus sebuah objek, maka
pada buku ini kita akan hampir selalu menggunakan ekspresi fungsi.
Setiap kali kita menciptakan fungsi, pada dasarnya kita membuat sebuah
objek
Function
baru,
dengan nama yang kita berikan. Karenanya, secara eksplisit menuliskan
bahwa kita membuat objek baru dan memperlakukan objek tersebut sama
seperti perintah-perintah lain dalam program akan menyederhanakan kode
program kita, yang pada akhirnya akan mempermudah kita mengerti kode
kita nantinya.
Aturan
pembuatan fungsi, baik ekspresi fungsi maupun deklarasi fungsi, sama
dengan aturan penulisan ekspresi. Di mana kita dapat menuliskan
ekspresi, kita dapat mendefinisikan fungsi juga. Karena aturan ini, maka
kita juga dapat mendefinisikan fungsi di dalam fungsi lainnya. Fungsi
yang berada di dalam fungsi lainnya memiliki akses terhadap semua
variabel yang ada pada fungsi penampungnya. Keterhubungan fungsi di
dalam fungsi ini dikenal dengan nama closure. Kita akan membahas tentang closure dan melihat bagaimana closure memberikan kemampuan ekspresi yang sangat besar kepada pengembang pada bagian berikutnya.
Note
Terdapat satu lagi cara membuat fungsi pada Javascript, yaitu dengan menggunakan objek
Function
. Tetapi kita tidak akan membahas cara ini, karena cara yang ketiga akan sangat jarang digunakan.Fungsi sebagai Objek
Sebelum
melihat bagaimana fungsi dapat dipanggil, kita akan melihat
keterhubungan antara fungsi dengan objek terlebih dahulu. Kita perlu
mengerti hubungan antara fungsi dan objek karena terdapat empat cara
pemanggilan fungsi pada Javascript, dan dua dari empat cara tersebut
melibatkan konsep fungsi sebagai objek.
Fungsi pada javascript adalah sebuah objek. Sebagai sebuah objek, semua fungsi dalam Javascript merupakan turunan dari
Function.prototype
. Function.prototype
juga adalah merupakan turunan dari Object.prototype
,
sama seperti semua objek-objek lain dalam Javascript. Perbedaan utama
fungsi dengan objek lain pada umumnya adalah fungsi dapat dipanggil, dan
memiliki dua buah properti khusus, yaitu konteks pemanggilan fungsi dan
kode pada isi badan fungsi. Kegunaan dari dua buah properti khusus ini
akan kita lihat pada bagian selanjutnya.
Sebagai
sebuah objek, fungsi juga dapat kita perlakukan sama dengan objek
lainnya. Pada bagian sebelumnya kita telah melihat bahwa fungsi dapat
disimpan di dalam variabel. Fungsi juga dapat kita simpan di dalam array
atau objek lain, dikirimkan sebagai argumen dari fungsi lain, atau
dikembalikan dari sebuah fungsi. Sama seperti objek, kita juga dapat
mengaitkan fungsi (method) kepada fungsi.
Pemanggilan Fungsi
Sebuah
fungsi dapat dipanggil untuk menjalankan seluruh kode yang ada di dalam
fungsi tersebut, sesuai dengan parameter yang kita berikan. Pemanggilan
fungsi dilakukan dengan cara menuliskan nama fungsi tersebut, kemudian
mengisikan argumen yang ada di dalam tanda kurung.
Misalkan fungsi
tambah
yang kita buat pada bagian sebelumnya:1 2 3 4 | var tambah = function (a, b) {
var hasil = a + b;
return hasil;
};
|
dapat dipanggil seperti berikut:
1 | tambah(3, 5);
|
Yang terjadi pada kode di atas adalah kita menggantikan
a
dan b
masing-masing dengan 3
dan 5
.
Seperti yang dapat dilihat, hal ini berarti pengisian argumen pada saat
pemanggilan fungsi harus berurut, sesuai dengan deklarasi fungsi.
Sama seperti sebuah variabel, fungsi juga mengembalikan nilai ketika dipanggil. Dalam kasus di atas,
tambah(3, 5)
akan
mengembalikan nilai 8. Nilai ini tentunya dapat disimpan ke dalam
variabel baru, atau bahkan dikirimkan sebagai sebuah argumen ke fungsi
lain lagi:1 2 3 4 5 | var simpan = tambah(3, 5); // simpan === 8
tambah(simpan, 2); // mengembalikan 10
tambah(tambah(3, 5), 2) // juga mengembalikan 10
tambah(tambah(2, 3), 4) // mengembalikan 9
|
Fungsi akan mengembalikan nilai ketika kata kunci
return
ditemukan. Kita dapat mengembalikan fungsi kapanpun, dan fungsi akan segera berhenti ketika kata kunci return
ditemukan. Berikut adalah contoh kode yang memberikan gambaran tentang pengembalian nilai fungsi:1 2 3 4 5 6 7 8 9 10 | var naikkan = function (n) {
var hasil = n + 10;
return hasil;
// kode di bawah tidak dijalankan lagi
hasil = hasil * 100;
}
naikkan(10); // mengembalikan 20
naikkan(25); // mengembalikan 35
|
Kita juga dapat langsung memberikan ekspresi kepada
return
, dan ekspresi tersebut akan dijalankan sebelum nilai dikembalikan. Hal ini berarti fungsi tambah
maupun naikkan
yang sebelumnya bisa disederhanakan dengan tidak lagi menyimpan nilai di variabel hasil
terlebih dahulu:1 2 3 4 5 6 7 8 9 10 11 | var naikkan = function (n) {
return n + 10;
}
var tambah = function (a, b) {
return a + b;
}
tambah(4, 4); // mengembalikan 8
naikkan(10); // mengembalikan 20
tambah(naikkan(5), 7); // mengembalikan 22
|
Fungsi pada Javascript juga akan selalu mengembalikan fungsi. Ketika tidak menemukan perintah
return
, Javascript akan mengembalikan undefined
pada akhir fungsi.Pola Pemanggilan Fungsi
Ketika
sebuah fungsi dipanggil, secara otomatis Javascript akan memberikan dua
nilai tambahan kepada fungsi tersebut. Kedua nilai tambahan ini
diberikan bersamaan dengan pemberian nilai argumen fungsi. Adapun kedua
nilai yang diberikan adalah
this
dan arguments
.
Nilai
arguments
merupakan
sebuah objek yang mirip dengan array, dan berisi seluruh argumen yang
diberikan kepada fungsi. Kita akan membahas penggunaan nilai ini pada
bagian selanjutnya.
Nilai
this
isinya bergantung kepada cara kita memanggil fungsi. Cara pemanggilan fungsi dikenal dengan nama pola pemanggilan (invocation pattern) dari fungsi tersebut. Terdapat empat pola pemanggilan fungsi yang ada pada Javascript, yaitu:- Method Invocation Pattern,
- Function Invocation Pattern,
- Constructor Invocation Pattern, dan
- Indirect Invocation Pattern.
Mari kita lihat maksud dari masing-masing pola pemanggilan, dan perbedaan nilai
this
pada setiap pola.Method Invocation Pattern
Sebuah fungsi yang dijadikan sebagai properti dari objek dikenal dengan istilah method. Methodmerupakan
salah satu konsep dasar dalam pemrograman berorientasi objek, yang
digunakan untuk memberikan sebuah perintah standar bagi sebuah objek.
Berikut adalah contoh dari sebuah method:
1 2 3 4 5 6 7 8 9 | var papanSkor = {
skor: 0,
tambahSkor: function (nilai) {
this.skor += (typeof nilai === "number")? nilai : 1;
},
ambilSkor: function () {
return this.skor;
}
};
|
Objek
papanSkor
yang barusan kita buat memiliki satu buah properti, yaitu skor
dan dua buah method, yaitu tambahSkor
dan ambilSkor
.
Method
ambilSkor
mengembalikan nilai dari properti skor
yang sekarang, sementara tambahSkor
akan menambahkan nilai skor
sesuai dengan parameter yang diberikan oleh pengguna method. Method tambahSkor
juga
memberikan tambahan sesuai dengan tipe data yang dikirimkan: jika
parameter yang diberikan merupakan sebuah angka, maka penambahan
dilakukan sesuai dengan jumlah angka yang dikirimkan, jika tidak maka skor
akan bertambah satu saja.
Sama seperti properti, pemanggilan method dapat dilakukan dengan dua cara, yaitu dengan menggunakan tanda titik (
.
) dan kurung siku ([]
):1 2 3 4 5 | papanSkor.ambilSkor() // mengembalikan 0
papanSkor.tambahSkor(1) // mengembalikan undefined
papanSkor["ambilSkor"]() // mengembalikan 1
papanSkor["tambahSkor"](2) // mengembalikan undefined
papanSkor.ambilSkor() // mengembalikan 3
|
Dari ekspreimen yang kita lakukan pada kode di atas, kita dapat melihat bahwa properti
this
berisi objek yang menampung dirinya. Dalam hal ini nilai this
pada Javascript tidak berbeda dengan nilaithis
pada
bahasa pemrograman berorientasi objek lain pada umumnya. Artinya kita
dapat mengakses seluruh properti maupun method dari objek itu sendiri
ketika menggunakan this
.Function Invocation Pattern
Ketika sebuah fungsi bukan merupakan properti dari sebuah objek (method), nilai tambahan
this
akan dihubungkan ke objek global Javascript, yaitu window
. Mari kita lihat bagaimana this
dikaitkan ke objek global:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // Variabel nilai disimpan dalam objek global.
// Objek global Javascript secara standar adalah window
// sehingga nilai akan disimpan dalam window.nilai
var nilai = 100;
nilai; // mengembalikan 100
window.nilai; // mengembalikan 100
var kurang = function (n) {
// this.nilai merupakan window.nilai (!)
this.nilai = this.nilai - n;
};
kurang(10);
nilai; // mengembalikan 90 (!)
window.nilai; // mengembalikan 90 (!)
// Hal yang sama berlaku untuk fungsi di dalam fungsi juga
var tambah_sepuluh = function () {
var tambah = function (n) {
// this.nilai merupakan window.nilai (!!!)
this.nilai = this.nilai + n;
};
tambah(10);
};
nilai; // mengembalikan 90
tambah_sepuluh();
nilai; // mengembalikan 100 (!!!)
|
Pada kode di atas, kita dapat melihat bagaimana nilai
this
di dalam fungsi kurang
maupuntambah
terikat dengan variabel global. Tidak peduli berapa tingkat fungsi di dalam fungsi yang kita buat, variabel this
tetap
akan mengakses objek global. Hal ini tidak hanya berbeda dengan pada
bahasa pemrograman lain, tetapi merupakan salah satu kesalahan besar
dalam rancangan Javascript. Pada bahasa yang memiliki fitur fungsional
dan objek lainnya, this
akan mengikat pada fungsi induk dari fungsi tersebut. Dalam contoh di atas, this
pada fungsi tambah
akan mencari variabelnilai
milih fungsi tambah_sepuluh
.
Kesalahan perancangan Javascript ini sangat fatal, karena teknik memanfaatkan fungsi di dalam fungsi (inner function) merupakan salah satu teknik mendasar dan sangat berguna dalam pemrograman fungsional. Tetapi setidaknya untuk inner function dari sebuah method kita memiliki solusi untuk memperbaiki hal ini, yaitu dengan menyimpan nilai
this
pada
fungsi luar ke dalam sebuah variabel. Fungsi yang berada di dalam
fungsi luar kemudian dapat mengakses instan objek dengan menggunakan
variabel tersebut, seperti berikut:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // kita menggunakan objek papanSkor yang di atas kembali
papanSkor.hattrick = function () {
// variabel that dibuat agar fungsi tambah_tiga
// dapat mengakses objek papanSkor.
var that = this;
var tambah_tiga = function() {
// jika kita menggunakan this di sini,
// maka kita akan mengakses variabel global skor,
// yang tidak ada.
that.skor = that.skor + 3;
};
tambah_tiga();
};
|
Sebagai eksperimen, coba ganti variable
that
di atas dengan variabel this
, dan lihat efeknya!
Note
Nama variabel
that
merupakan perjanjian tak tertulis (konvensi) untuk solusi ini. Meskipun tidak ada aturan untuk wajib menggunakan that
, menggunakan nama ini akan mempermudah pengembang lain yang harus merawat kode anda (termasuk anda sendiri di masa depan!).Constructor Invocation Pattern
Sebuah fungsi yang dipanggil dengan diawali dengan perintah
new
pada Javascript dikenal dengan istilah constructor invocation. Setiap kali sebuah fungsi dipanggil dengan prefiks new
, maka fungsi tersebut akan otomatis mengembalikan objek baru pada akhir fungsi, meskipun kita tidak memanggil perintah return
. Objek yang dikembalikan ini akan dihubungkan kepada prototype
dari fungsi yang dipanggil, dan this
diikatkan kepada objek baru ini.
Bingung? Mari kita lihat contoh kode:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Ketika dipanggil dengan new, fungsi Manusia akan
// mengembalikan sebuah objek baru yang memiliki
// satu properti, yaitu "nama".
var Manusia = function (nama) {
this.nama = nama;
};
var andi = new Manusia("Andi");
andi.nama; // mengembalikan "Andi"
andi; // mengembalikan { nama: 'Andi' }
// Seperti layaknya sebuah objek, kita dapat menambahkan
// method baru kepada seluruh objek Manusia yang telah
// dibuat.
Manusia.prototype.usia = function (usia) {
this.usia = (typeof usia === "number")? usia: 0;
};
andi.usia = 27;
andi.usia; // mengembalikan 27
andi; // mengembalikan { nama: 'Andi', usia: 27 }
|
Perhatikan bagaimana pada fungsi Manusia di atas kita sama sekali tidak memanggil perintah
return
, dan secara otomatis andi
diisikan dengan sebuah objek baru ketika kita memanggilManusia
dengan perintah new
. Juga seperti objek pada Javascript, ketika kita menambahkan sebuahmethod baru ke Manusia
, andi
yang hanyalah salah satu turunan Manusia
juga menerima methodbaru tersebut.
Fungsi yang dirancang untuk dipanggil bersamaan dengan
new
seperti Manusia
dikenal dengan nama constructor. Constructor juga
secara konvensi ditulis dengan awalan huruf kapital, agar tidak
membingungkan pengembang. Hal ini sangat penting karena sebuah fungsi constructor yang tidak dipanggil dengan perintah new
akan
memberikan efek samping yang membahayakan, yaitu memenuhi atau mengubah
nilai variabel global. Baca kembali bagian Function Invocation Pattern
untuk melihat kenapa hal ini bisa terjadi.1 2 3 4 5 6 | var mari = Manusia("Mari");
mari; // mengembalikan undefined
nama; // mengembalikan Mari (!)
mari.nama; // mengembalikan TypeError:
// Cannot read property 'nama' of undefined
|
Karena memiliki potensi berbahaya seperti ini, sangat tidak disarankan untuk menggunakan fungsiconstructor. Javascript bukanlah bahasa berorientasi objek class-based. Pergunakan Javascript sebagaimana ia dirancang untuk digunakan, yaitu bahasa pemrograman prototype-based.
Pada bagian selanjutnya nanti kita akan melihat bagaimana kita dapat
membuat objek pada Javascript dengan lebih baik, yaitu menggunakan closure.
Indirect Invocation Pattern
Karena fungsi pada Javascript juga adalah merupakan sebuah objek, maka fungsi juga dapat memilikimethod. Terdapat beberapa method standar yang selalu ada pada fungsi Javascript, tetapi di bagian ini kita akan melihat dua method khusus yang berhubungan dengan nilai
this
. Kedua methodkhusus ini yaitu call
dan apply
.
Method
apply
digunakan
jika kita ingin mengirimkan argumen ke sebuah fungsi dengan menggunakan
array. Terdapat dua parameter yang harus kita kirimkan ke apply
, yaitu objek yang ingin kita ikatkan kepada this
, dan parameter keduanya adalah sebuah array yang akan digunakan sebagai parameter yang dikirimkan ke fungsi. Dengan begitu, apply
memberikan kita fasiliats untuk menentukan nilai this
.1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var manusia = {
nama: "Adam",
panggil: function (sapaan) {
return sapaan + " " + this.nama + "!";
}
};
manusia.panggil("Halo"); // mengembalikan "Halo Adam!"
var hawa = {
nama: "Hawa"
};
manusia.panggil.apply(hawa, ["Bonjour"]); // mengembalikan "Bonjour Hawa!"
|
Kita juga dapat mengirimkan
null
sebagai parameter pertama dari apply
untuk menggunakan objek global sebagai this
.1 2 3 4 5 6 7 8 9 | // mengembalikan "Guten Tag undefined!" karena tidak ada variabel "nama"
// pada konteks global
manusia.panggil.apply(null, ["Guten Tag"]);
// simpan variabel "nama" dengan isi "Nuh" pada konteks global
nama = "Nuh";
// mengembalikan "Guten Tag Nuh!"
manusia.panggil.apply(null, ["Guten Tag"]);
|
Hal
ini memungkinkan kita untuk menggunakan fungsi global secara efektif.
Misalnya jika kita ingin mencari nilai maksimal dari sebuah array, kita
dapat langsung menggunakan
Math.max
daripada dengan perulangan:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var bil = [5, 6, 2, 3, 7];
// Sama dengan Math.apply(bil[0], bil[1], ...)
// berapapun ukuran bil fungsi ini akan tetap berjalan
var max = Math.max.apply(null, bil);
max; // mengembalikan 7
// Tanpa menggunakan apply
// Kita tidak dapat menggunakan Math.max di sini
// karena panjang array tidak akan selalu sama.
max = -Infinity;
for (var i = 0; i < bil.length; i++) {
if (bil[i] > max)
max = bil[i];
}
max; // mengembalikan 7
|
Satu hal yang perlu diingat ketika menggunakan
apply
adalah
bahwa Javascript memiliki batas jumlah argumen untuk fungsi, yang
berbeda-beda pada setiap browser. Jika array yang dikirimkan melebihi
batas jumlah argumen maka apa yang terjadi tidak dapat diketahui
(tergantung kepada pembuat browser).
Method
call
sendiri berfungsi sama seperti apply
, dengan hanya satu perbedaan: call
menerima *daftar argumen* seperti fungsi biasa, sementara apply
menerima *array argumen*. Mengambil contoh manusia
sebelumnya, kita dapat memanggil call
seperti berikut:1 2 3 4 5 6 7 | // kedua fungsi di bawah mengembalikan "Bonjour Hawa!"
manusia.panggil.apply(hawa, ["Bonjour"]);
manusia.panggil.call(hawa, "Bonjour");
// kedua fungsi di bawah hasilnya sama
Math.max.apply(null, bil);
Math.max.call(null, bil[0], bil[1], ...);
|
Argumen Fungsi
Selain
this
, fungsi pada Javascript juga memiliki satu buah nilai tambahan lagi, yaitu arguments
.arguments
merupakan
nilai yang menampung seluruh argumen yang dikirimkan kepada fungsi,
termasuk argumen-argumen yang berlebihan. Jika fungsi hanya meminta dua
buah argumen dan pemanggil fungsi mengirimkan empat buah argumen, kita
dapat mengakses argumen ketiga dan keempat menggunakan arguments
. Hal ini berarti kita dapat membuat fungsi yang bisa menerima jumlah argumen tak tentu, seperti fungsi Math.max
yang kita gunakan sebelumnya.
Contoh lain, kita dapat membuat fungsi yang menghitung total dari seluruh argumen yang dikirimkan kepada fungsi tersebut:
1 2 3 4 5 6 7 8 9 10 | var total = function () {
var i, hasil = 0;
for (i = 0; i < arguments.length; i++) {
hasil = hasil + arguments[i];
}
return hasil;
};
total(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // mengembalikan 55
|
Meskipun pada kode di atas kita menggunakan
arguments
seperti layaknya array (akses dengan []
, properti length
), sayangnya arguments
bukan array. arguments
merupakan sebuah objek yangmirip array. Selalu ingat hal ini agar tidak menggunakan arguments
sebagai array, karena menggunakan arguments
sebagai
array dapat menyebabkan hal-hal yang tak terbayangkan seperti gangguan
pernafasan, serangan jantung, kanker, dan error program tiba-tiba.Scope dan Function Scope
Dalam konteks bahasa pemrograman, scope atau cakupan merupakan sebuah aturan yang mengendalikan waktu hidup (lifetime) dan akses dari sebuah nilai dalam program. Nilai yang dikendalikan oleh scope umumnya adalah variabel. Scope sangat
penting dalam pemrograman, terutama untuk memudahkan kita dalam
menamakan variabel dan manajemen memori. Dalam Java misalnya, sebuah
variabel yang dibuat dalam scope lokal akan segera dihapuskan dari memori begitu program keluar dari scope tersebut untuk menghemat memori.
Kebanyakan bahasa pemrograman mengimplementasikan konsep scope seperti yang ada pada Java. Konsep scope ini dikenal dengan nama block scope. Pada block scope, sebuah variabel yang diciptakan di dalam blok (ditandai dengan
{}
pada
Java dan keluarga C) tidak dapat diakses dari luar blok tersebut.
Variabel juga biasanya dihapus ketika program keluar dari blok jika
bahasa mendukung manajemen memori otomatis. Block scope sangat intuitif dan mudah dipahami, serta hampir selalu digunakan oleh kebanyakan bahasa pemrograman.
Javascript,
sayangnya, bukanlah bahasa pemrograman biasa. Meskipun Javascript
menggunakan sintaks yang sama dengan keluarga bahasa C, Javascript tidak
memiliki block scope. Aturan scopingyang digunakan oleh Javascript adalah function scope. Hal ini mengakibatkan banyak kesalah pahaman tentang aturan scoping di Javascript. Sekarang kita akan menjernihkan kesalah pahaman tersebut.
Pada Javascript, sebuah blok *tidak* membuat scope baru. Perhatikan kode berikut:
1 2 3 4 5 6 7 8 9 | var bil = 1;
console.log(bil); // mencetak 1
if (bil > 0) {
var bil = 2;
console.log(bil); // mencetak 2
}
console.log(bil); // mencetak 2 (!)
|
Pada kode di atas, ketika kita menuliskan
var bil = 2;
kita tidak membuat sebuah variabel lokal bernama bil
di dalam blok if
. Kita hanya mengisikan kembali bil
dengan nilai baru, yaitu 2
. Pada bahasa keluarga C, jika kita mencetak bil
pada akhir program kita akan mendapatkan nilai 1
, karena bil
yang ada di dalam if
berbeda dengan bil
yang
ada di luarnya. Hal ini sedikit tidak intuitif, terutama untuk yang
telah mempelajari bahasa pemrograman lain, dan jika tidak hati-hati kita
dapat secara tidak sadar membuat banyak variabel global.
Aturan scoping yang dimiliki Javascript hanyalah function scope. Function scope berarti
semua parameter dan variabel yang dibuat di dalam sebuah fungsi tidak
dapat diakses di luar fungsi tersebut. Perhatikan kode berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 | var x = 10;
console.log(x); // mencetak 10
var tambah = function (n) {
var x = 3;
console.log(x); // mencetak 3
return n + x;
}
var y = tambah(15);
console.log(y); // mencetak 18 (15 + 3)
console.log(x); // mencetak 10
|
Dalam contoh kode kali ini, kita dapat melihat bagaimana Javascript membedakan
x
yang ada di luar fungsi tambah
dengan x
yang ada di dalam fungsi. Begitu keluar dari fungsi tambah
, nilai x
yang diberikan adalah x
yang di luar fungsi. Aturan ini mirip dengan aturan yang ada dalam C. Dengan memanfaatkan pengetahuan function scope ini, kita dapat membuat scope lokal seperti block scope dalam Javascript dengan memanfaatkan fungsi anonim. Mari kita lihat bagaimana hal ini dicapai:1 2 3 4 5 6 7 8 9 10 11 12 | var bil = 1;
console.log(bil); // mencetak 1
if (bil > 0) {
// Buat sebuah fungsi anonim dan langsung eksekusi fungsi
(function() {
var bil = 2;
console.log(bil); // mencetak 2
})();
}
console.log(bil); // mencetak 1 (!)
|
Pada contoh kode di atas, nilai
bil
yang ada di luar if
berbeda dengan bil
yang ada di dalamif
. Kita membuat sebuah fungsi anonim dalam if
, dengan menggunakan semantik(function() { // isi fungsi })
. Tanda kurung (()
)
yang membungkus fungsi digunakan untuk memastikan fungsi dijalankan
sebagai ekspresi, bukan perintah. Sementara tanda kurung yang ada di
akhir fungsi bertugas untuk mengeksekusi fungsi anonim yang baru dibuat.
Dengan
membuat sebuah fungsi anonim seperti contoh di atas, kita memastikan
seluruh variabel yang ada di dalam fungsi anonim tersebut memiliki scope yang
berbeda dengan variabel lain di luar fungsi anonim. Eksekusi fungsi
akan memastikan seluruh kode di dalam fungsi berjalan, karena ingat
bahwa kode di dalam fungsi tidak akan dijalankan kalau fungsi tidak
dipanggil.
Meskipun kita telah dapat membuat block scope dalam Javascript, terkadang kita tetap saja dapat secara tidak sengaja membuat kesalahan program karena asumsi scope yang
salah. Hal ini terutama terjadi ketika kita berpindah dari bahasa yang
digunakan di sisi server ke Javascript. Untuk menghindari kesalahan ini,
biasanya praktisi Javascript menuliskan semua deklarasi variabel sedini
mungkin, ketika program atau fungsi baru akan dimulai. Hal ini
bertentangan dengan bahasa pemrograman lain yang biasanya menyarankan
untuk mendeklarasikan variabel hanya ketika akan menggunakan variabel
tersebut. Walaupun sedikit berbeda, deklarasi variabel sedini mungkin
akan membantu program Javascript kita: jika menemukan
var
di tengah-tengah fungsi, seorang pengembang Javascript berpengalaman akan mengecek scope variabel tersebut, dan memindahkan deklarasi ini ke awal fungsi / program.Closure
Salah satu kelebihan utama function scope adalah
fungsi yang ada di dalam fungsi lainnya juga memiliki akses terhadap
semua nilai-nilai yang dimiliki fungsi penampungnya. Ketika sebuah
fungsi menggunakan dan bergantung kepada nilai yang ada di luar dirinya,
fungsi tersebut dikenal dengan nama closure. Mari kita lihat contoh sebuah closure:
1 2 3 4 5 6 7 8 9 10 | var jalan = function () {
var sapaan = "Halo ",
sapa = function () {
console.log(sapaan + "pembaca!");
};
sapa();
};
jalan(); // mencetak "Halo pembaca!"
|
Fungsi
jalan
memiliki dua buah nilai lokal, yaitu variabel sapaan
dan fungsi sapa
. Fungsi sapa
menggunakan variabel sapaan
, dan akan mencetak teks yang berbeda-beda tergantung dengan isi dari variabel sapaan
. Variabel sapaan
dikenal dengan nama variabel bebas karena tidak terikat dengan apapun, dan fungsi sapa
merupakan closure karena menggunakan variabel bebas yang dibuat di luar dirinya.
Oke, closure keren dan kelihatannya sederhana. Pertanyaan selanjutnya tentunya adalah, apakegunaan utama dari closure? Pada contoh di atas, kita bisa saja langsung menerima parameter
sapaan
pada fungsi jalan
, dan langsung mencetak sapaan sesuai dengan fungsi sapa
kan? Terus kenapa kita harus memanggil fungsi sapa
lagi di akhir jalan
? Kalau bisa mudah, kenapa harus dibuat sulit seperti sekarang?
Jawaban
dari seluruh pertanyaan-pertanyaan di atas adalah potensi kemampuan
penyusunan (komposisi) fungsi serta penggunaan kembali kode yang
ditawarkan oleh closure. Contoh di atas memang tidak memperlihatkan penggunaan closure yang optimal. Mari kita tingkatkan fungsi
jalan
sedikit
demi sedikit. Misalkan, kita dapat membuat fungsi ini mengembalikan
fungsi lain, sehingga pengguna fungsi dapat membangun sapaannya sendiri:1 2 3 4 5 6 7 8 9 10 11 | var buatSapaan = function (sapaan) {
return function () {
console.log(sapaan + " pembaca!");
}
};
var sapaanJerman = buatSapaan("Guten");
var sapaanInggris = buatSapaan("Hello");
sapaanJerman(); // mencetak "Guten pembaca!"
sapaanInggris(); // mencetak "Hello pembaca!"
|
Pada contoh di atas, kita mengirimkan nilai berupa kata sapaan dalam bahasa yang diinginkan kepada
buatSapaan
, dan buatSapaan
akan
membuat sebuah fungsi baru kepada kita. Fungsi baru ini bersifat
anonim, dan dapat mencetak kata sapaan yang kita berikan sebelumnya.
Terdapat dua hal yang menarik dari contoh kode di atas:- Variabel (parameter)
sapaan
adalah milikbuatSapaan
, tetapi variabel ini tetap dapat digunakan olehsapaanJerman
dansapaanInggris
ketikabuatSapaan
telah selesai dijalankan. Biasanya variabelsapaan
akan dihapus begitu fungsibuatSapaan
selesai dijalankan. - Meskipun menggunakan (dan bergantung kepada)
sapaan
, baiksapaanJerman
maupunsapaanInggris
tidak dapat mengakses :code:`sapaan`. Hal ini berarti kedua fungsi ini tidak dapat mengganti nilaisapaan
lagi!
Fungsi
sapaanInggris
dan sapaanJerman
juga adalah merupakan closure. Lengkapnya, sebuahclosure terdiri
dari dua komponen: fungsi dan lingkungan eksekusi fungsi tersebut.
Lingkungan dari fungsi berisi variabel maupun fungsi lokal yang ada ketika fungsi tersebut dibuat. Closure dapat mengakses dan menggunakan nilai dalam lingkungannya, tetapi pengguna closure tidak mendapatkan akses tersebut.
Fungsi
buatSapaan
masih dapat kita kembangkan lebih lanjut lagi, misalkan dengan menambahkan parameter pada closure yang dikembalikan:1 2 3 4 5 6 7 8 9 10 11 | var buatSapaan = function (sapaan) {
return function (target) {
console.log(sapaan + " " + target + "!");
}
};
var sapaanJerman = buatSapaan("Guten");
var sapaanInggris = buatSapaan("Hello");
sapaanJerman("Soryu"); // mencetak "Guten Soryu!"
sapaanInggris("Holmes"); // mencetak "Hello Holmes!"
|
Pada contoh kali ini, baik
sapaanJerman
maupun sapaanInggris
dapat
menerima sebuah parameter: nama yang akan disapa. Kita dapat mengubah
dan menambahkan banyak fungsionalitas lain lagi pada buatSapaan
, tetapi poin yang paling penting adalah kita dapat menuliskan fungsi yang membuat fungsi lain secara dinamis. Para pengembang program berorientasi objek mengenal hal ini dengan nama factory.
Salah satu hal penting yang juga ditawarkan oleh closure adalah akses nilai lingkungannya. Karena kita dapat mengakses dan mengubah nilai pada lingkungan closure, maka kita juga dapat memanfaatkan closure untuk membuat sebuah objek yang memiliki properti private. Bayangkan kalau kita membuat closure yang mengembalikan objek:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | var penghitung = function () {
// variabel hitungan di sini menjadi
// variabel private yang tidak dapat
// diakses dari luar objek.
var hitungan = 0;
return {
tambahHitungan: function () {
hitungan = hitungan + 1;
},
ambilNilai: function () {
return hitungan;
}
};
};
var jumlahHarimau = penghitung();
jumlahHarimau.hitungan; // undefined (seolah-olah tidak ada)
jumlahHarimau.tambahHitungan();
jumlahHarimau.ambilNilai(); // mengembalikan 1
jumlahHarimau.tambahHitungan();
jumlahHarimau.tambahHitungan();
jumlahHarimau.ambilNilai(); // mengembalikan 3
|
Dalam contoh di atas, fungsi
penghitung
pada dasarnya adalah sebuah constructor karena
fungsi ini membuat objek untuk kita. Keuntungan tambahan dari membuat
objek dengan cara ini yaitu kita bisa membuat properti privat. Method tambahHitungan
dan ambilNilai
sebagai closure tetap dapat mengakses hitungan
, karena hitungan
masih merupakan variabel dalam lingkungan closure. Karena objek yang dikembalikan tidak diikatkan dengan hitungan
, maka kita tidak dapat mengakseshitungan
melalui objek yang dihasilkan ini. Perhatikan bagaimana pada kode di atas ketika kita mencoba mengakses hitungan
kita diberikan nilai undefined
, seolah-oleh variabel tersebut tidak ada.
Properti
privat memiliki sangat banyak kegunaan, terutama kalau kita merancang
pustaka atau framework untuk digunakan orang lain. Ketika kita
menambahkan fungsionalitas yang masih bersifat eksperimental misalnya,
biasanya kita tidak ingin seluruh variabel yang digunakan oleh fungsi
tersebut ke pengguna pustaka. Masih ada banyak kegunaan lain dari
properti privat, tetapi kita tidak akan membahasnya sekarang.
Terakhir, kita juga dapat membuat sebuah objek secara langsung dengan mengeksekusi closurebegitu selesai dibuat. Berikut adalah contoh pembuatannya:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var penghitung = (function () {
var hitungan = 0;
return {
tambahHitungan: function () {
hitungan = hitungan + 1;
},
ambilNilai: function () {
return hitungan;
}
};
})();
penghitung.tambahHitungan();
penghitung.ambilNilai(); // mencetak 1
penghitung.tambahHitungan();
penghitung.tambahHitungan();
penghitung.tambahHitungan();
penghitung.ambilNilai(); // mencetak 4
|
Sampai
bagian ini, kita telah melihat bagaimana menggunakan fungsi untuk tidak
hanya membuat objek, tetapi juga membuat fungsi lain secara dinamis.
Kita akan melihat lebih banyak lagi contoh-contoh pemanfaatan fungsi
sebagai alat komposisi, abstraksi, maupun penggunaan kembali kode pada
bab selanjutnya.
Kesalahan umum Pembuatan Closure
Sebelum menutup pembahasan mengenai fungsi, kita akan melihat satu kesalahan umum yang sering ditemui ketika membuat closure.
Kesalahan yang umum dilakukan ini sangat “halus”, sehingga seringkali
menjebak bahkan seorang penegmbang yang berpengalaman sekalipun.
Kesalahan seperti apa yang akan kita lihat?
1 2 3 4 5 6 7 8 9 10 11 12 | var daftarFungsi = [];
var i;
for (var i = 0; i < 3; i++) {
daftarFungsi.push(function () {
return i;
});
}
daftarFungsi[0](); // mengembalikan 3 (!)
daftarFungsi[1](); // mengembalikan 3 (!)
daftarFungsi[2](); // mengembalikan 3 (!)
|
Pada kode di atas, closure yang dibuat untuk mengisikan
daftarFungsi
akan selalu mengembalikan 3. Secara intuitif, kita akan berasumsi bahwa karena nilai i
yang diikatkan ke masing-masing closureberbeda isinya (0, 1, dan 2) maka ketika fungsi yang dipanggil kita juga akan mendapatkan 0, 1, dan 2. Hal ini terjadi karena closure yang dibuat diikatkan kepada variabel i
, bukan nilai variabel i
ketika closure dibuat.
Solusi umum dari kesalahan ini adalah dengan membuat closure tambahan:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var daftarFungsi = [],
i,
pembuatFungsi = function (n) {
return function () {
return n;
}
};
for (var i = 0; i < 3; i++) {
daftarFungsi.push(pembuatFungsi(i));
}
daftarFungsi[0](); // mengembalikan 0
daftarFungsi[1](); // mengembalikan 1
daftarFungsi[2](); // mengembalikan 2
|
Sederhananya,
hindari pembuatan fungsi di dalam perulangan. Selain menghabiskan
banyak memori, pembuatan fungsi di dalam perulangan juga rawan akan
kesalahan seperti yang dijelaskan di atas. Jika terpaksa harus
mengikatkan fungsi ke dalam banyak nilai (misalnya membuat semua elemen
<a>
melakukan sesuatu yang sama ketika di klik), gunakan teknik fungsi pembantu yang mengembalikan closure seperti kode di atas.
Tidak ada komentar:
Posting Komentar