例で説明されたJavaScriptのクロージャ

クロージャとは何ですか?

クロージャは、関数と、その関数が宣言された字句環境(スコープ)の組み合わせです。クロージャは、Javascriptの基本的で強力なプロパティです。この記事では、クロージャーの「方法」と「理由」について説明します。

//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

もう一つの例

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 } ]

クロージャーは、それが作成された環境を「記憶」します。この環境は、クロージャーが作成されたときにスコープ内にあったローカル変数で構成されます。

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 

クロージャは、データを「記憶」し、返された関数を介してそのデータを操作できるため、便利です。これにより、JavaScriptは他のプログラミング言語にあるプライベートメソッドをエミュレートできます。プライベートメソッドは、コードへのアクセスを制限したり、グローバル名前空間を管理したりするのに役立ちます。

プライベート変数とメソッド

クロージャは、プライベートデータ/メソッドをカプセル化するためにも使用できます。この例を見てください:

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

この例ではbalancebankAccount関数の外部からアクセスすることはできません。つまり、プライベート変数を作成したばかりです。閉鎖はどこですか?さて、何bankAccount()が戻ってくるか考えてみてください。実際には、内部に多数の関数を含むオブジェクトが返されますが、を呼び出すaccount.getBalance()と、関数はへの最初の参照を「記憶」することができbalanceます。これがクロージャの力であり、関数がその字句スコープの外で実行された場合でも、関数はその字句スコープ(コンパイル時スコープ)を「記憶」します。

ブロックスコープの変数をエミュレートします。

Javascriptには、ブロックスコープ変数の概念がありませんでした。たとえば、forloop内で変数を定義する場合、この変数はforloopの外側からも表示されます。では、クロージャーはこの問題の解決にどのように役立つのでしょうか。見てみましょう。

 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 }

変数iにはブロックスコープがないため、3つの関数すべての値がループカウンターで更新され、悪意のある値が作成されました。クロージャは、関数が作成されたときの環境のスナップショットを作成し、その状態を保持することで、この問題の解決に役立ちます。

 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 }

javascript es6 +の最新バージョンには、変数にブロックスコープを与えるために使用できるletという新しいキーワードがあります。上で説明したような問題を解決するための専用の関数(forEach)とライブラリ全体(lodash.js)も多数あります。それらは確かにあなたの生産性を高めることができます、しかし何か大きなものを作成しようとするとき、これらすべての問題の知識を持つことは非常に重要です。

クロージャには、大規模なJavaScriptプログラムを作成するときに役立つ多くの特別なアプリケーションがあります。

  1. プライベート変数のエミュレートまたはカプセル化
  2. 非同期サーバー側呼び出しを行う
  3. ブロックスコープの変数を作成します。

プライベート変数のエミュレート。

他の多くの言語とは異なり、Javascriptには、オブジェクト内にカプセル化されたインスタンス変数を作成できるメカニズムがありません。パブリックインスタンス変数があると、中規模から大規模のプログラムを構築するときに多くの問題が発生する可能性があります。ただし、クロージャを使用すると、この問題を軽減できます。

前の例と同様に、オブジェクトのローカル変数にアクセスできるメソッドを使用して、オブジェクトリテラルを公開せずに返す関数を作成できます。したがって、それらを効果的にプライベートにします。

クロージャは、グローバルな名前空間を管理して、グローバルに共有されるデータとの衝突を回避するのにも役立ちます。通常、すべてのグローバル変数はプロジェクト内のすべてのスクリプト間で共有されます。これにより、中規模から大規模のプログラムを構築するときに、間違いなく多くの問題が発生します。そのため、ライブラリとモジュールの作成者はクロージャを使用して、モジュール全体のメソッドとデータを非表示にします。これはモジュールパターンと呼ばれ、特定の機能のみを外部にエクスポートする即時呼び出し関数式を使用して、グローバル参照の量を大幅に削減します。

これは、モジュールスケルトンの短いサンプルです。

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

クロージャは、「記憶された」環境に含まれるプライベート変数の新しいインスタンスをキャプチャするのに役立ちます。これらの変数には、返された1つまたは複数の関数を介してのみアクセスできます。

ベクトル

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