diff options
Diffstat (limited to 'files/id/web/javascript/panduan/closures')
-rw-r--r-- | files/id/web/javascript/panduan/closures/index.html | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/files/id/web/javascript/panduan/closures/index.html b/files/id/web/javascript/panduan/closures/index.html new file mode 100644 index 0000000000..73cdbb7e15 --- /dev/null +++ b/files/id/web/javascript/panduan/closures/index.html @@ -0,0 +1,345 @@ +--- +title: Closures +slug: Web/JavaScript/Panduan/Closures +translation_of: Web/JavaScript/Closures +--- +<p>Closure adalah fungsi yang merujuk kepada variabel yang mandiri (bebas). </p> + +<p>Dengan kata lain, fungsi yang di definisikan di dalam closure 'mengingat' lingkungan dimana closure ini didefinisikan. </p> + +<p>Lihat contoh berikut:</p> + +<div> +<pre class="brush: js">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(); +</pre> +</div> + +<p><code style="font-size: 14px;">init()</code><span style="line-height: 1.572;"> membuat sebuah lokal variabel </span><code style="font-size: 14px;">name</code><span style="line-height: 1.572;"> dan kemudian memanggil fungsi </span><code style="font-size: 14px;">displayName()</code><span style="line-height: 1.572;">. </span><code style="font-size: 14px;">displayName()</code><span style="line-height: 1.572;"> adalah fungsi internal yang didefinisikan didalam</span><span style="line-height: 1.572;"> </span><code style="font-size: 14px;">init()</code><span style="line-height: 1.572;"> dan hanya dapat diakses di dalam fungsi tersebut. </span><code style="font-size: 14px;">displayName()</code><span style="line-height: 1.572;"> tidak memiliki lokal variabelnya sendiri, namun fungsi ini memiliki akses ke variabel diluar fungsinya dan dapat menggunakan variabel </span><code style="font-size: 14px;">name</code><span style="line-height: 1.572;"> tersebut yang telah di deklarasikan di fungsi induknya.</span></p> + +<p><a href="http://jsfiddle.net/xAFs9/3/" title="http://jsfiddle.net/xAFs9/">Jalankan</a> kode dan perhatikan bahwa <code>alert()</code> dapat menampilkan isi dari variabel <code>name</code>, dimana variabel tersebut dideklarasikan pada fungsi induknya. Ini adalah sebuah contoh dari ruang lingkup leksikal (<em>lexical</em> <em>scoping)</em>, 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.</p> + +<p>Lihat contoh berikut:</p> + +<pre class="brush: js">function makeFunc() { + var name = "Mozilla"; + function displayName() { + alert(name); + } + return displayName; +} + +var myFunc = makeFunc(); +myFunc(); +</pre> + +<p>Jika kamu menjalankan kode ini, kode ini akan memiliki efek yang sama seperti contoh sebelumnya <code>init()</code>: teks "Mozilla" akan muncul di JavaScript alert box. Yang membedakan dan menarik adalah fungsi internal <code>displayName()</code> di kembalikan terlebih dahulu ke fungsi di luar sebelum di eksekusi.</p> + +<p>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 <code>makeFunc()</code> selesai dieksekusi, sewajarnya variabel <code>name</code> ini tidak dapat diakses lagi. Namun, karena kode ini masih berjalan normal, ini adalah hal yang berbeda di Javascript.</p> + +<p>Alasannya adalah fungsi tersebut telah menjadi <em>closure </em>di javascript. <em>Closure </em>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 <em>closure </em>dibuat. Pada kasus ini, <code>myFunc</code> bereferensi kepada fungsi <code>displayName</code> yang telah dibuat ketika <code>makeFunc</code> dijalankan. Fungsi <code>displayName</code> akan tepat menjaga akses ke lingkungan leksikalnya, dimana variabel <code>name</code> ini aktif. Untuk alasan inilah, ketika <code>myFunc</code> di panggil, variabel <code>name</code> tetap dapat digunakan dan "Mozilla" dikirim ke alert box.</p> + +<p>Berikut adalah contoh menarik yang lainnya — fungsi <code>makeAdder</code> :</p> + +<pre class="brush: js">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 +</pre> + +<p>Di contoh ini, kita telah mendefinisikan fungsi <code>makeAdder(x)</code> dengan satu argument <code>x</code> dan mengembalikan sebuah fungsi baru. Fungsi yang dikembalikan membutuhkan satu argumen <code>y</code>, dan mengembalikan jumlah <code>x</code> dan <code>y</code>.</p> + +<p>Esensinya, <code>makeAdder</code> 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.</p> + +<p><code>add5</code> dan <code>add10</code> keduanya adalah closure. Fungsi ini menggunakan definisi fungsi yang sama, namun menggunakan memori yang berbeda. Di <code>add5</code> variabel <code>x</code> memiliki nilai 5. sedangkan di <code>add10</code> variabel <code>x</code> memiliki nilai 10.</p> + +<h2 id="Penggunaan_Closure">Penggunaan Closure</h2> + +<p>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 <em>object oriented programming</em>, dimana objek obj0ek tersebut membantu kita dalam menghubukan beberapa data (properti objek) dengan satu atau lebih method.</p> + +<p>Karena itu, kamu dapat menggunakan closure dimanapun kamu dapat menggunakan objek dengan satu method.</p> + +<p>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.</p> + +<p>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 <em>pixel</em>, kemudian menentukan ukuran elemen lain di halaman (seperti header) menggunakan satuan unit <em>em</em>:</p> + +<pre class="brush: css">body { + font-family: Helvetica, Arial, sans-serif; + font-size: 12px; +} + +h1 { + font-size: 1.5em; +} + +h2 { + font-size: 1.2em; +} +</pre> + +<p>Tombol interaktif kita dapat merubah ukuran huruf dari elemen body dan elemen yang lainnya akan menyesuaikan.</p> + +<p>Berikut kode Javascript:</p> + +<pre class="brush: js">function makeSizer(size) { + return function() { + document.body.style.fontSize = size + 'px'; + }; +} + +var size12 = makeSizer(12); +var size14 = makeSizer(14); +var size16 = makeSizer(16); +</pre> + +<p><code>size12</code>, <code>size14</code>, dan <code>size16</code> 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:</p> + +<pre class="brush: js">document.getElementById('size-12').onclick = size12; +document.getElementById('size-14').onclick = size14; +document.getElementById('size-16').onclick = size16; +</pre> + +<pre class="brush: html"><a href="#" id="size-12">12</a> +<a href="#" id="size-14">14</a> +<a href="#" id="size-16">16</a> +</pre> + +<p><a href="https://jsfiddle.net/vnkuZ">Lihat pada JSFiddle</a></p> + +<h2 id="Emulating_private_methods_with_closures">Emulating private methods with closures</h2> + +<p>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.</p> + +<p>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.</p> + +<p>Here's how to define some public functions that can access private functions and variables, using closures which is also known as the <a class="external" href="http://www.google.com/search?q=javascript+module+pattern" title="http://www.google.com/search?q=javascript+module+pattern">module pattern</a>:</p> + +<pre class="brush: js">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 */ +</pre> + +<p>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: <code>counter.increment</code>, <code>counter.decrement</code>, and <code>counter.value</code>.</p> + +<p>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 <code>privateCounter</code> and a function called <code>changeBy</code>. 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.</p> + +<p>Those three public functions are closures that share the same environment. Thanks to JavaScript's lexical scoping, they each have access to the <code>privateCounter</code> variable and <code>changeBy</code> function.</p> + +<p>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 <code>counter</code> variable. We could store this function in a separate variable <code>makeCounter </code>and use it to create several counters.</p> + +<pre class="brush: js">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 */ +</pre> + +<p>Notice how each of the two counters maintains its independence from the other. Its environment during the call of the <code>makeCounter()</code> function is different each time. The closure variable <code>privateCounter </code>contains a different instance each time.</p> + +<p>Using closures in this way provides a number of benefits that are normally associated with object oriented programming, in particular data hiding and encapsulation.</p> + +<h2 id="Creating_closures_in_loops_A_common_mistake">Creating closures in loops: A common mistake</h2> + +<p>Prior to the introduction of the <a href="/en-US/docs/JavaScript/Reference/Statements/let" title="let"><code>let</code> keyword</a> in JavaScript 1.7, a common problem with closures occurred when they were created inside a loop. Consider the following example:</p> + +<pre class="brush: html"><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> +</pre> + +<pre class="brush: js">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(); +</pre> + +<p><a href="https://jsfiddle.net/v7gjv">Lihat pada JSFiddle</a></p> + +<p>The <code>helpText</code> 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.</p> + +<p>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.</p> + +<p>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 <code>setupHelp</code> 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 <code>helpText</code> list.</p> + +<p>One solution in this case is to use more closures: in particular, to use a function factory as described earlier on:</p> + +<pre class="brush: js">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(); +</pre> + +<p><a href="https://jsfiddle.net/v7gjv/1">Lihat pada JSFiddle</a></p> + +<p>This works as expected. Rather than the callbacks all sharing a single environment, the <code>makeHelpCallback</code> function creates a new environment for each one in which <code>help</code> refers to the corresponding string from the <code>helpText</code> array.</p> + +<h2 id="Performance_considerations">Performance considerations</h2> + +<p>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.</p> + +<p>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).</p> + +<p>Consider the following impractical but demonstrative case:</p> + +<pre class="brush: js">function MyObject(name, message) { + this.name = name.toString(); + this.message = message.toString(); + this.getName = function() { + return this.name; + }; + + this.getMessage = function() { + return this.message; + }; +} +</pre> + +<p>The previous code does not take advantage of the benefits of closures and thus could instead be formulated:</p> + +<pre class="brush: js">function MyObject(name, message) { + this.name = name.toString(); + this.message = message.toString(); +} +MyObject.prototype = { + getName: function() { + return this.name; + }, + getMessage: function() { + return this.message; + } +}; +</pre> + +<p>However, redefining the prototype is not recommended, so the following example is even better because it appends to the existing prototype:</p> + +<pre class="brush: js">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; +}; +</pre> + +<p>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 <a href="/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model">Details of the Object Model</a> for more details.</p> + +<h2 id="Expression_closures">Expression closures</h2> + +<p>This addition is nothing more than a shorthand for writing simple functions, giving the language something similar to a typical <a class="external" href="http://en.wikipedia.org/wiki/Lambda_calculus#Lambda_calculus_and_programming_languages">Lambda notation</a>.</p> + +<p>JavaScript 1.7 and older:</p> + +<pre class="brush: js">function(x) { return x * x; }</pre> + +<p>JavaScript 1.8:</p> + +<pre class="brush: js">function(x) x * x</pre> + +<p>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.</p> + +<p><strong>Examples:</strong></p> + +<p>A shorthand for binding event listeners:</p> + +<pre class="brush: js"> document.addEventListener("click", function() false, true); +</pre> + +<p>Using this notation with some of the array functions from JavaScript 1.6:</p> + +<pre class="brush: js">elems.some(function(elem) elem.type == "text");</pre> |