Object, Function, Scope, Prototype, dan Closure Pada Javascript


Tulisan ini adalah hasil terjemahan bebas dari artikel berjudul serupa: The JavaScript guide to Objects, Functions, Scope, Prototypes and Closures yang ditulis oleh Sudhir Jonathan

Sepanjang perjalanan saya menulis kode javascript, saya sadar walau saya senang dengan dialek bahasa yang ada, tapi sedikit sulit untuk dimengerti. Banyak yang mendasarkan ini pada (memang harus diakui memang tidak begitu bagus) desain bahasa, atau penyimpangan implementasi dari yang umum digunakan bahasa lainnya. Yang manapun itu, dengan mengerti sedikit kebenaran yang ada bisa membuat anda pergi jauh dengan javascript. Apa yang akan dijabarkan setelah ini adalah versi tertulis dan ringkas dari kelas pengenalan javascript yang saya berikan pada program pelatihan dan user groups.

Mari kita mulai dengan object dan variable.

var name = 'zaphod';
var age = 42;

Ini cukup mudah. Keyword var membatasi scope dari variable dan mencegah mereka dari menjadi global variable tanpa kita ketahui. ini akan kita bicarakan lebih lanjut nanti.

Bagaimana dengan:

var person = {name: 'zaphod', age: 42};

Sedikit lebih menarik. Apa yang kita lakukan di sini adalah membuat satu variable, person. Variable ini berisi nama dan umur yang tersimpan dalam suatu penyimpanan sederhana key-value… sebuah map, bila anda inginkan. Menariknya, ternyata semua object javascript bisa dianggap seperti ini; mereka semua pada intinya adalah map key-store, atau dictionaries.

Mengakses data pada object ini cukup mudah

alert(person.name);
alert(person.age);

Yang lebih menarik lagi adalah kode di atas sama dengan

alert(person['name']);
alert(person['age']);

Biasanya ini adalah ‘aha’ momen pada kebanyakan orang, karena mulai memberikan ide samar kemampuan yang bisa diberikan dari pendekatan di atas. Nanti akan dijelaskan lebih jauh. Mari lanjut.

var sayHello = function(){
    alert('Hello.');    
};

Ada banyak yang bisa dipelajari dari tiga baris kode di atas. Pertama, mirip sekali seperti bagaimana kita biasanya mendefinisikan object. Karena, memang demikian adanya. Function pada javascript sejatinya adalah object yang dimuliakan. Sebenarnya, bahkan tidak terlalu mulia – mereka hanya object yang bisa ‘dipanggil’.

var sayHello = function(name, age) {
    return "Hello, I'm " + name + " and I'm " + age + "years old.";
}

Cukup sederhana. Function ini mengambil argumen dan mengembalikan nilai. Lebih baik lagi, bisa ditulis:

var sayHello = function(person) {
    return "Hello, I'm " + person.name + " and I'm " + person.age + "years old.";
}

Sejauh ini cukup bagus. Panggil function dengan parameter person, dan anda akan mendapatkan yang anda inginkan.

sayHello(person);

Jadi, cara menganggil object function adalah dengan menggunakan (), dengan kemungkinan penyertaan argumen di dalamnya.

Tapi, kalau function adalah object, apa yang bisa menghentikan kita dari melakukan ini?

person.doSomething = sayHello;

person.doSomething(person) akan memberikan hasil yang sama. Itu cukup bagus. Tapi person terlalu banyak ditulis. Bila kita memanggil function dari person, kenapa menyertakan person lagi? Mari kita coba yang lain:

var sayHello = function() {
    return "Hello, I'm " + this.name + " and I'm " + this.age + "years old.";
}

Di sini kita pertama kali melihat scope. Scope adalah environment, atau dunia, di mana function dijalankan. Pada javascript, default scope untuk menjalankan function adalah object Window dari halaman tersebut, yang mana adalah object level browser yang merepresentasikan suatu jendela (atau tab) di mana halaman berada.

Itu artinya menjalankan sayHello() (sendirian) akan menghasilkan error – this.name akan mengacu pada object tab saat ini name, tapi saya kira tab anda tidak akan memiliki property age. Itu adalah sesuatu yang harus diperhatikan; jika function hanya merefer ke this.name, dia akan selalu jalan, tapi mungkin tidak akan membuat hasil seperti yang anda harapkan.

Ini akan memberikan apa yang kita mau:

person.doSomething = sayHello;
person.doSomething();

Sekarang, saat doSomething jalan, dia akan dieksekusi pada scope person. Sehingga this.name dan this.age akan mengunakan data dari object person. Untuk lebih eksplisit, anda bisa selalu menulis sayHello.call(person); yang mana akan menjalankan sayHello pada konteks dari person. Method call() sangat berguna pada kode yang berhubungan dengan scope.

Tapi kita belum memodifikasi sayHello. Sehingga, memanggil sayHello() akan tetap menghasilkan error. doSomething hanya memiliki referensi ke function yang sama yang dimiliki oleh sayHello. Anda bisa mengkonfirmasi ini dengan mengubah sayHello ke sesuatu yang lain. person.doSomething akan tetap berkerja seperti yang dinginkan.

Berikut ini potongan kode menarik lainnya:

var zaphod = {name: 'Zaphod', age: 42};
var marvin = {name: 'Marvin', age: 420000000000;}

zaphod.sayHello = function(){
    return "Hi, I'm " + this.name;
}

zaphod.sayHello() akan memberikan hasil “Hi, I’m Zaphod”. Trick question: Apa hasil dari marvin.sayHello()?

Itu satu hal lagi yang perlu diingat tentang scope. Selalu berada pada saat runtime. Yang artinya scope dari suatu function selalu berada pada context di mana dia dijalankan, bukan pada context di mana dia didefinisikan. Sehingga marvin.sayHello() akan menghasilkan “Hi, I’m Marvin”. Kita sudah melihat ini pada contoh sebelumnya: pada saat scope tidak didefinisikan secara eksplisit, function sejadinya didefinisikan menggunakan context dari Window.

Mari gunakan apa yang sudah kita lihat sejauh ini untuk membuat kalkulator sederhana.

var plus = function(x,y){ return x + y };
var minus = function(x,y){ return x - y };

var operations = {
    '+': plus,
    '-': minus
};

Cara functional untuk menggunakannya kira-kira seperti ini:

var calculate = function(x, y, operation){
    return operations[operation](x, y);
}

calculate(38, 4, '+');
calculate(47, 3, '-');

Contoh ini menunjukkan luar biasa mudahnya mengimplementasikan beberapa patterns seperti strategy pattern di javascript.

Selain itu, mari kita coba membuat sesuatu yang lebih object-oriented. Sementara kebanyakan bahasa ‘object oriented’ saat ini menggunakan inheritance berbasis class, javascript menggunakan prototypes. Tidak ada class constructor di sini, tapi tetap menjadi bahasa yang sangat powerful. mari kita lihat bagaimana cara kerjanya:

var Problem = function(x, y){
    this.x = x;
    this.y = y;        
}

var problem1 = new Problem(4, 5);

Di sini kita punya function yang mengambil dua parameter, x dan y dan menyimpannya. Keyword new sangat penting di sini karena dia memberitahukan interpreter bahwa kita menggunakan Problem bukan sebagin function, melainkan sebagai constructor. Menanggalkannya akan mengakibatkan function Problem tereksekusi, yang mana, tidak mengambalikan apa-apa, sehingga problem1 tidak akan memiliki nilai. Keyword new memberikan kita copy dari function object setelah menjalankannya, sementara yang asli tetap ada (agar kita bisa menggunakannya untuk membuat lebih banyak object Problem).

Ingat bahwa Problem itu sendiri adalah function yang sangat normal – kita mengubah menjadi kapital ‘P’ hanya sebagai kebiasaan untuk mengingatkan kita agar menggunakan keyword new, dan semua perilaku constructor yang ada pada penggunaan keyword new.

Selain itu juga, perlu diingat bahwa function adalah object, maka kita bisa melakukan ini:

Problem.prototype.operations = {
    '+': function(x,y){ return x + y; },
    '-': function(x,y){ return x - y; }
};

Problem.prototype.calculate = function(operation){
    return this.operations[operation](this.x, this.y);
};

problem1.calculate('+');
problem1.calculate('-');

Di sini kita pertama kali melihat prototype pada javascript. Walaupun prototype adalah salah satu pondasi pada javascript, tapi sangat mungkin menulis javascript bertahun-tahun tanpa memiliki satupun alasan untuk menggunakannya. Tapi seperti kebanyakan hal, mengerti prototype sangat penting untuk menguasai javascript.

Pada javascript, setiap object memiliki property bawaan yang disebut __proto__. Properti ini menuju ke object lain yang dianggap sebagai prototype object ini, semacam induk atau cetakan dari mana object ini dibuat. __proto__ adalah kaitan yang sangat berguna karena ini artinya semua object memiliki property dan method dari prototypenya.

Sebaliknya, mengubah prototype dan menambah feature baru akan membuat semua feature baru tersebut tersedia ke semua object yang memiliki prototype tersebut.

Model prototype pada javascript berkerja serupa dengan model class pada bahasa lainnya, dalam arti apabila ada method yang tidak ditemukan pada defini class object tersebut, superclass-nya akan dicek, kemudian superclass dari superclass tersebut, begitu seterusnya. Proses yang sama juga terjadi di sini – pertama object tersebut dicek. Bila method atau properti tidak ditemukan, __proto__-nya akan dicek, kemudian __proto__ dari prototype-nya dan seterusnya, sampai interpreter menyentuk object terakhir pada rangkaian yang ada (__proto__ berisi null).

Anda pastinya akan menyadari, bahwa kita belum menyentuh __proto__ dimanapun dalam kode kita. Itu karena melakukan hal tersebut sangat tidak masuk akal dan biasanya merupakan ide buruk. Tapi javascript memungkinkan kita menggunakan prototype object pada function yang digunakan sebagai constructor (pada kasus ini, Problem) untuk menspesifikasi perilaku dari prototype dari object yang dibuat. Javascript akan otomatis memasangkan untuk anda __proto__ dari object yang dibuat ke Problem.prototype.

Sekarang cukup mudah untuk melihat bahwa memanggil problem1.calculate() sejatinya pertama kali akan memanggil problem1. Karena tidak menemukan method calculate, interpreter akan melihat ke __proto__, yang pada dasarnya sama dengan Problem.prototype, dimana dia akan menemukan method tersebut. Interpreter kemudian akan menjalankan calculate pada scope problem1. Beginilah cara bagaimana method bisa didefinisikan hanya pada prototype-nya dan masih bisa berjalan dengan benar pada semua object yang memiliki prototype tersebut.

Kita juga akan menyadari bahwa proses penemuan prototype ini (seperti pada kebanyakan kasus di javascript) adalah pada saat runtime. Kita menambahkan method ke Problem.prototype setelah membuat object problem1 dan problem2, tapi tetap tidak menyebabkan masalah menggunakan method ini pada problem1 dan problem2. Interpreter memproses pemanggilan function pada saat kita memanggil mereka.

Mari kita tambahkan method terakhir ke Problem:

Problem.prototype.newMessageMaker = function(){
    var self = this;
    var formatter = function(){
        return 'Values: ' + self.x + ' and ' + self.y;
    };

    return function(start, end){
        return '' + start + formatter() + end;
    };
}

var messageMaker = problem1.newMessageMaker();
// Do lots of things.
var message = messageMaker('This was the problem: ', 'Thanks for solving it!');
alert(message);

Contoh di atas memang mengada-ada, tapi itu mengilustrasikan bagian paling penting dari javascript – konsep dari closure. Di sini, problem1.newMessageMaker() mengembalikan function lain, yang bisa anda panggil sesuka hati.

Hal yang menarik di sini adalah function formatter. Kita tahu formatter didefinisikan di dalam function newMessageMaker, tapi newMessageMaker dieksekusi dan selesai jauh sebelum messageMaker sebenarnya digunakan. Lalu apa yang terjadi dengan formatter? bukankah seharusnya dia sudah hilang?

Jawabannya adalah bahwa function yang dikembalikan oleh newMessageMaker mengcover variable formatter. Dia menyimpan nilai dari formatter lama setelah semuanya dilepas, termasuk juga function yang aslinya mendefinisikan hal tersebut.

Ini juga seperti bagaimana trik dengan variable self bekerja. Kita tidak bisa menggunakannya pada kasus ini karena kita tidak tahu ada di mana scope dari messageMaker. Maka kita memberikannya ke beberapa variable (di sini kita menggunakan self) dan menggunakannya di dalam function yang akan kita berikan. Dengan cara ini, closure akan memastikan bahwa data yang benar yang selalu digunakan, tak perduli di mana scope dari function ini berjalan.

Closure sangat berguna terutama pada kode AJAX. Anda akan lebih sering menemukan situasi dimana anda akan memberikan function ke librari AJAX untuk dijalankan setelah pemanggilan server selesai. Jika anda ingin agar function tersebut memiliki akses ke data spesial yang anda tidak ingin agar diakses oleh library tersebut, closure adalah cara terbaik melakukannya.

Semua ini sebenarnya masih bagian kecil dari belajar menguasai bahasa javascript dan masih banyak lagi yang perlu diketahui, tapi, seperti biasa, mengotori tangan anda dan langsung menulis kode serta membaca kode-kode bagus dari orang lain adalah cara yang baik untuk terus maju.

Tulisan ini adalah hasil terjemahan bebas dari artikel berjudul serupa: The JavaScript guide to Objects, Functions, Scope, Prototypes and Closures yang ditulis oleh Sudhir Jonathan

Author: Arief Bayu Purwanto

Hello, my name is Arief Bayu Purwanto, a 24 years old father of a beautiful daughter. Interested in online programming, linux, games, and reading. Currently working on kapanlagi.com as junior developer. I live in a relatively quite and cool place called Malang. I'm available for some freelance stuff as well as some consulting job. You can see my portofolio for some previous task I've finished and some other information related to my capability. Btw, I'm plurking here.

  • duh gusti, i’m totally lost…