Penutupan dalam JavaScript Dijelaskan dengan Contoh

Apa itu Penutupan?

Penutupan adalah gabungan fungsi dan lingkungan leksikal (ruang lingkup) di mana fungsi tersebut dinyatakan. Penutupan adalah hak milik Javascript yang asas dan kuat. Artikel ini membincangkan 'bagaimana' dan 'mengapa' mengenai Penutupan:

Contohnya

//we have an outer function named walk and an inner function named fly function walk (){ var dist = '1780 feet'; function fly(){ console.log('At '+dist); } return fly; } var flyFunc = walk(); //calling walk returns the fly function which is being assigned to flyFunc //you would expect that once the walk function above is run //you would think that JavaScript has gotten rid of the 'dist' var flyFunc(); //Logs out 'At 1780 feet' //but you still can use the function as above //this is the power of closures

Contoh yang lain

function by(propName) { return function(a, b) { return a[propName] - b[propName]; } } const person1 = {name: 'joe', height: 72}; const person2 = {name: 'rob', height: 70}; const person3 = {name: 'nicholas', height: 66}; const arr_ = [person1, person2, person3]; const arr_sorted = arr_.sort(by('height')); // [ { name: 'nicholas', height: 66 }, { name: 'rob', height: 70 },{ name: 'joe', height: 72 } ]

Penutupan 'mengingati' persekitaran di mana ia dibuat. Lingkungan ini terdiri dari mana-mana pemboleh ubah tempatan yang berada dalam ruang lingkup pada saat penutupan dibuat.

function outside(num) { var rememberedVar = num; // In this example, rememberedVar is the lexical environment that the closure 'remembers' return function inside() { // This is the function which the closure 'remembers' console.log(rememberedVar) } } var remember1 = outside(7); // remember1 is now a closure which contains rememberedVar = 7 in its lexical environment, and //the function 'inside' var remember2 = outside(9); // remember2 is now a closure which contains rememberedVar = 9 in its lexical environment, and //the function 'inside' remember1(); // This now executes the function 'inside' which console.logs(rememberedVar) => 7 remember2(); // This now executes the function 'inside' which console.logs(rememberedVar) => 9 

Penutupan berguna kerana mereka membiarkan anda 'mengingat' data dan kemudian membiarkan anda beroperasi pada data tersebut melalui fungsi yang dikembalikan. Ini membolehkan javascript meniru kaedah peribadi yang terdapat dalam bahasa pengaturcaraan lain. Kaedah peribadi berguna untuk menyekat akses ke kod serta mengurus ruang nama global anda.

Pemboleh ubah dan kaedah peribadi

Penutupan juga dapat digunakan untuk merangkum data / kaedah peribadi. Lihat contoh ini:

const bankAccount = (initialBalance) => { const balance = initialBalance; return { getBalance: function() { return balance; }, deposit: function(amount) { balance += amount; return balance; }, }; }; const account = bankAccount(100); account.getBalance(); // 100 account.deposit(10); // 110

Dalam contoh ini, kita tidak dapat mengakses balancedari mana saja di luar bankAccountfungsi, yang bermaksud kita baru saja membuat pemboleh ubah peribadi. Di mana penutupannya? Baiklah, fikirkan apa yang bankAccount()akan kembali. Ini sebenarnya mengembalikan Objek dengan sekumpulan fungsi di dalamnya, tetapi ketika kita memanggil account.getBalance(), fungsi tersebut dapat "mengingat" rujukan awalnya balance. Itulah kekuatan penutupan, di mana fungsi "mengingat" skop leksikalnya (kompilasi ruang waktu), bahkan ketika fungsi tersebut dijalankan di luar ruang lingkup leksikal itu.

Meniru pemboleh ubah blok-skop.

Javascript tidak mempunyai konsep pemboleh ubah blok-skop. Maksudnya apabila menentukan pemboleh ubah di dalam forloop misalnya, pemboleh ubah ini dapat dilihat dari luar forloop juga. Jadi bagaimana penutupan dapat membantu kita menyelesaikan masalah ini? Mari kita lihat.

 var funcs = []; for(var i = 0; i < 3; i++){ funcs[i] = function(){ console.log('My value is ' + i); //creating three different functions with different param values. } } for(var j = 0; j < 3; j++){ funcs[j](); // My value is 3 // My value is 3 // My value is 3 }

Oleh kerana pemboleh ubah i tidak mempunyai ruang lingkup blok, nilainya dalam ketiga fungsi tersebut dikemas kini dengan penghitung gelung dan mencipta nilai jahat. Penutupan dapat membantu kita menyelesaikan masalah ini dengan membuat gambaran mengenai lingkungan ketika fungsi itu dibuat, menjaga keadaannya.

 var funcs = []; var createFunction = function(val){ return function() {console.log("My value: " + val);}; } for (var i = 0; i < 3; i++) { funcs[i] = createFunction(i); } for (var j = 0; j < 3; j++) { funcs[j](); // My value is 0 // My value is 1 // My value is 2 }

Versi akhir javascript es6 + mempunyai kata kunci baru yang disebut let yang boleh digunakan untuk memberikan pemboleh ubah blokcope. Terdapat juga banyak fungsi (forEach) dan keseluruhan perpustakaan (lodash.js) yang dikhaskan untuk menyelesaikan masalah seperti yang dijelaskan di atas. Mereka pasti dapat meningkatkan produktiviti anda, namun sangat penting untuk mengetahui semua masalah ini ketika berusaha membuat sesuatu yang besar.

Penutupan mempunyai banyak aplikasi khas yang berguna semasa membuat program javascript yang besar.

  1. Meniru pemboleh ubah peribadi atau enkapsulasi
  2. Membuat panggilan sampingan pelayan Asynchronous
  3. Membuat pemboleh ubah blok-skop.

Meniru pemboleh ubah persendirian.

Tidak seperti banyak bahasa lain, Javascript tidak mempunyai mekanisme yang membolehkan anda membuat pemboleh ubah contoh yang dikemas dalam objek. Mempunyai pemboleh ubah contoh awam boleh menyebabkan banyak masalah ketika membina program sederhana hingga besar. Namun dengan penutupan, masalah ini dapat dikurangkan.

Seperti contoh sebelumnya, anda boleh membina fungsi yang mengembalikan literal objek dengan kaedah yang mempunyai akses ke pemboleh ubah tempatan objek tanpa memaparkannya. Oleh itu, menjadikannya berkesan secara peribadi.

Penutupan juga dapat membantu anda mengurus ruang nama global anda untuk mengelakkan pertembungan dengan data yang dikongsi secara global. Biasanya semua pemboleh ubah global dikongsi antara semua skrip dalam projek anda, yang pasti akan memberi anda banyak masalah ketika membina program sederhana hingga besar. Itulah sebabnya mengapa pengarang perpustakaan dan modul menggunakan penutupan untuk menyembunyikan keseluruhan kaedah dan data modul. Ini disebut pola modul, ia menggunakan ekspresi fungsi yang segera dipanggil yang hanya mengeksport fungsi tertentu ke dunia luar, mengurangkan jumlah rujukan global secara signifikan.

Berikut adalah contoh kerangka modul.

var myModule = (function() = { let privateVariable = 'I am a private variable'; let method1 = function(){ console.log('I am method 1'); }; let method2 = function(){ console.log('I am method 2, ', privateVariable); }; return { method1: method1, method2: method2 } }()); myModule.method1(); // I am method 1 myModule.method2(); // I am method 2, I am a private variable

Penutupan berguna untuk menangkap contoh baru pemboleh ubah peribadi yang terdapat dalam lingkungan 'diingat', dan pemboleh ubah tersebut hanya dapat diakses melalui fungsi atau metode yang dikembalikan.

Vektor

A vector is perhaps the most simple type of collection in Clojure. You can think of it like an array in Javascript. Let’s define a simple vector:

(def a-vector [1 2 3 4 5]) ;; Alternatively, use the vector function: (def another-vector (vector 1 2 3 4 5)) ;; You can use commas to separate items, since Clojure treats them as whitespace. (def comma-vector [1, 2, 3, 4, 5])

You’ll see that it uses square brackets, just like an array in JS. Since Clojure, like JS, is dynamically typed, vectors can hold elements of any type, including other vectors.

(def mixed-type-vector [1 "foo" :bar ["spam" 22] #"^baz$"])

Adding items to a vector

You can append items to a vector using conj. You can also prepend to a list using into, but note that into is intended for merging two vectors, so both its arguments must be vectors, and using into is slower than using conj.

(time (conj [1 2] 3)) ; => "Elapsed time: 0.032206 msecs" ; [1 2 3] (time (into [1] [2 3])) ; => "Elapsed time: 0.078499 msecs" ; [1 2 3]
:rocket:

IDEOne it!

Retrieving items from a vector

You can retrieve items from a vector using get. This is equivalent to using bracket notation to access items in an array in many imperative languages. Items in a vector are 0-indexed, counting from the left.

var arr = [1, 2, 3, 4, 5]; arr[0]; // => 1

In Clojure, this would be written like so:

(def a-vector [1 2 3 4 5]) (get a-vector 0) ; => 1

You can also give get a default value, if you give it an index that isn’t in the array.

;; the list doesn't have 2147483647 elements, so it'll return a string instead. (get a-vector 2147483646 "sorry, not found!") ; => "sorry, not found!"

Converting other collections into vectors

Non-vector data structures can be converted into vectors using the vec function. With hashmaps, this produces a 2D vector containing pairs of keys and values.

(vec '(1 2 3 4 5)) ; => [1 2 3 4 5] (vec {:jack "black" :barry "white"}) ; => [[:jack "black"] [:barry "white"]]

When to use a vector?

A vector should be used in almost all cases if you need a collection, because they have the shortest random-access times, which makes it easy to retrieve items from a vector. Note that vectors are ordered. If order doesn’t matter, it may be better to use a set. Also note that vectors are designed for appending items; if you need to prepend items, you might want to use a list.

More info on Closures:

  • Learn JavaScript closures in six minutes
  • A basic guide to closures in JavaScript
  • Discover the power of closures in VueJS
  • JavaScript closures explained by mailing a package