Closures

You’re reading the English version of this content since no translation exists yet for this locale. Help us translate this article!

Closure adalah fungsi yang merujuk kepada variabel yang mandiri (bebas). 

Dengan kata lain, fungsi yang di definisikan di dalam closure 'mengingat' lingkungan dimana closure ini didefinisikan. 

Lihat contoh berikut:

function init() {
    var name = "Mozilla"; // name adalah sebuah lokal variabel yang dibuat oleh init
    function displayName() { // displayName() adalah fungsi internal, sebuah closure
        alert (name); // displayName() menggunakan variabel yang dideklarasikan pada fungsi induknya    
    }
    displayName();    
}
init();

init() membuat sebuah lokal variabel name dan kemudian memanggil fungsi displayName(). displayName() adalah fungsi internal yang didefinisikan didalam init() dan hanya dapat diakses di dalam fungsi tersebut. displayName() tidak memiliki lokal variabelnya sendiri, namun fungsi ini memiliki akses ke variabel diluar fungsinya dan dapat menggunakan variabel name tersebut yang telah di deklarasikan di fungsi induknya.

Jalankan kode dan perhatikan bahwa alert() dapat menampilkan isi dari variabel name, dimana variabel tersebut dideklarasikan pada fungsi induknya. Ini adalah sebuah contoh dari ruang lingkup leksikal (lexical scoping), yang menunjukan bagaimana cara javascript mencari variabel. Di Javascript lokasi dimana variabel tersebut dideklarasikan di dalam source code menentukan dimana variabel itu dapat diakses. Nested functions memiliki akses pada variabel yang dideklarasikan pada ruang lingkup induknya.

Lihat contoh berikut:

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

Jika kamu menjalankan kode ini, kode ini akan memiliki efek yang sama seperti contoh sebelumnya init(): teks "Mozilla" akan muncul di JavaScript alert box. Yang membedakan dan menarik adalah fungsi internal displayName() di kembalikan terlebih dahulu ke fungsi di luar sebelum di eksekusi.

Jika dilihat ini agak aneh karena normalnya pada bahasa pemrograman lain, variabel lokal di dalam sebuah fungsi hanya ada saat fungsi tersebut dieksekusi. Sehingga saat makeFunc() selesai dieksekusi, sewajarnya variabel name ini tidak dapat diakses lagi. Namun, karena kode ini masih berjalan normal, ini adalah hal yang berbeda di Javascript.

Alasannya adalah fungsi tersebut telah menjadi closure di javascript. Closure adalah kombinasi dari fungsi dan lingkungan leksikal dimana fungsi itu di deklarasikan. Lingkungan ini terdiri dari lokal variabel yang berada di ruang lingkup yang sama saat closure dibuat. Pada kasus ini, myFunc bereferensi kepada fungsi displayName yang telah dibuat ketika makeFunc dijalankan. Fungsi displayName akan tepat menjaga akses ke lingkungan leksikalnya, dimana variabel name ini aktif. Untuk alasan inilah, ketika myFunc di panggil, variabel name tetap dapat digunakan dan "Mozilla" dikirim ke alert box.

Berikut adalah contoh menarik yang lainnya — fungsi makeAdder :

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

Di contoh ini, kita telah mendefinisikan fungsi makeAdder(x) dengan satu argument x dan mengembalikan sebuah fungsi baru. Fungsi yang dikembalikan membutuhkan satu argumen y, dan mengembalikan jumlah x dan y.

Esensinya, makeAdder adalah fungsi untuk membuat fungsi (function factory) — fungsi ini akan membuat fungsi yang akan menambahkan angka melalui argumen. Pada contoh diatas kita membuat dua fungsi baru — yang satu menambahkan 5 melalui argumentnya dan satu menambahkan 10.

add5 dan add10 keduanya adalah closure. Fungsi ini menggunakan definisi fungsi yang sama, namun menggunakan memori yang berbeda. Di add5 variabel x memiliki nilai 5. sedangkan di add10 variabel x memiliki nilai 10.

Penggunaan Closure

Setelah membaca teorinya muncul pertanyaan —  Apakah closure berguna? Mari kita lihat implikasi dari penggunaan closure. Closure membantu kita mengakses data (pada lingkungannya) dengan fungsi yang mengoperasikan data tersebut. Ini berhubungan dengan object oriented programming, dimana objek obj0ek tersebut membantu kita dalam menghubukan beberapa data (properti objek) dengan satu atau lebih method.

Karena itu, kamu dapat menggunakan closure dimanapun kamu dapat menggunakan objek dengan satu method.

Situasi ini banyak ditemui umumnya pada pengembangan web. Banyak kode yang kita tulis di Javascript berdasarkan event — kita definisikan terlebih dahulu sifat dari event ini, kemudian menempelkannya pada event yang di panggil oleh user (seperti klik atau penekanan tombol). Kode kita secara garis umum adalah sebuah callback: sebuah fungsi yang dijalankan untuk merespon sebuah event.

Berikut adalah contoh: kita ingin menambahkan beberapa tombol di sebuah halaman yang akan merubah ukuran teks. Cara untuk melakukannya adalah dengan menentukan ukuran huruf dari elemen body dalam satuan unit pixel, kemudian menentukan ukuran elemen lain di halaman (seperti header) menggunakan satuan unit em:

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

Tombol interaktif kita dapat merubah ukuran huruf dari elemen body dan elemen yang lainnya akan menyesuaikan.

Berikut kode Javascript:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12, size14, dan size16 adalah fungsi yang akan merubah ukuran teks body ke 12, 14, dan 16 pixel, secara berurutan. Kemudian kita tempelkan fungsi ini ke tombol (pada kasus ini adalah link) sebagai berikut:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a> 

Lihat pada JSFiddle

Emulating private methods with closures

Languages such as Java provide the ability to declare methods private, meaning that they can only be called by other methods in the same class.

JavaScript does not provide a native way of doing this, but it is possible to emulate private methods using closures. Private methods aren't just useful for restricting access to code: they also provide a powerful way of managing your global namespace, keeping non-essential methods from cluttering up the public interface to your code.

Here's how to define some public functions that can access private functions and variables, using closures which is also known as the module pattern:

var counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };   
})();

alert(counter.value()); /* Alerts 0 */
counter.increment();
counter.increment();
alert(counter.value()); /* Alerts 2 */
counter.decrement();
alert(counter.value()); /* Alerts 1 */

There's a lot going on here. In previous examples each closure has had its own environment; here we create a single environment which is shared by three functions: counter.increment, counter.decrement, and counter.value.

The shared environment is created in the body of an anonymous function, which is executed as soon as it has been defined. The environment contains two private items: a variable called privateCounter and a function called changeBy. Neither of these private items can be accessed directly from outside the anonymous function. Instead, they must be accessed by the three public functions that are returned from the anonymous wrapper.

Those three public functions are closures that share the same environment. Thanks to JavaScript's lexical scoping, they each have access to the privateCounter variable and changeBy function.

You'll notice we're defining an anonymous function that creates a counter, and then we call it immediately and assign the result to the counter variable. We could store this function in a separate variable makeCounter and use it to create several counters.

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* Alerts 0 */
counter1.increment();
counter1.increment();
alert(counter1.value()); /* Alerts 2 */
counter1.decrement();
alert(counter1.value()); /* Alerts 1 */
alert(counter2.value()); /* Alerts 0 */

Notice how each of the two counters maintains its independence from the other. Its environment during the call of the makeCounter() function is different each time. The closure variable privateCounter contains a different instance each time.

Using closures in this way provides a number of benefits that are normally associated with object oriented programming, in particular data hiding and encapsulation.

Creating closures in loops: A common mistake

Prior to the introduction of the let keyword in JavaScript 1.7, a common problem with closures occurred when they were created inside a loop. Consider the following example:

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp(); 

Lihat pada JSFiddle

The helpText array defines three helpful hints, each associated with the ID of an input field in the document. The loop cycles through these definitions, hooking up an onfocus event to each one that shows the associated help method.

If you try this code out, you'll see that it doesn't work as expected. No matter what field you focus on, the message about your age will be displayed.

The reason for this is that the functions assigned to onfocus are closures; they consist of the function definition and the captured environment from the setupHelp function's scope. Three closures have been created, but each one shares the same single environment. By the time the onfocus callbacks are executed, the loop has run its course and the item variable (shared by all three closures) has been left pointing to the last entry in the helpText list.

One solution in this case is to use more closures: in particular, to use a function factory as described earlier on:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp(); 

Lihat pada JSFiddle

This works as expected. Rather than the callbacks all sharing a single environment, the makeHelpCallback function creates a new environment for each one in which help refers to the corresponding string from the helpText array.

Performance considerations

It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated to the object's prototype rather than defined into the object constructor. The reason is that whenever the constructor is called, the methods would get reassigned (that is, for every object creation).

Consider the following impractical but demonstrative case:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

The previous code does not take advantage of the benefits of closures and thus could instead be formulated:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

However, redefining the prototype is not recommended, so the following example is even better because it appends to the existing prototype:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

In the two previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See Details of the Object Model for more details.

Expression closures

This addition is nothing more than a shorthand for writing simple functions, giving the language something similar to a typical Lambda notation.

JavaScript 1.7 and older:

function(x) { return x * x; }

JavaScript 1.8:

function(x) x * x

This syntax allows you to leave off the braces and 'return' statement - making them implicit. There is no added benefit to writing code in this manner, other than having it be syntactically shorter.

Examples:

A shorthand for binding event listeners:

 document.addEventListener("click", function() false, true);

Using this notation with some of the array functions from JavaScript 1.6:

elems.some(function(elem) elem.type == "text");