web-dev-qa-db-ja.com

JavaScriptのクロージャーの実用的な用途は何ですか?

私は 試行中 JavaScriptクロージャに頭を包むのが一番難しい.

内部関数を返すことで、その直接の親で定義された変数にアクセスできます。

これはどこで役立つのでしょうか?おそらく、まだ頭を抱えていない。 私がオンラインで見た例 のほとんどは、実際のコードを提供せず、曖昧な例にすぎません。

誰かが私に閉鎖の実際の使用を見せてもらえますか?

たとえば、これですか?

var warnUser = function (msg) {
    var calledCount = 0;
    return function() {
       calledCount++;
       alert(msg + '\nYou have been warned ' + calledCount + ' times.');
    };
};

var warnForTamper = warnUser('You can not tamper with our HTML.');
warnForTamper();
warnForTamper();
253
alex

私はクロージャーを使って次のようなことをしました:

a = (function () {
    var privatefunction = function () {
        alert('hello');
    }

    return {
        publicfunction : function () {
            privatefunction();
        }
    }
})();

ご覧のとおり、aはオブジェクトであり、クロージャーの内部にのみ存在するpublicfunctionを呼び出すメソッドprivatefunctiona.publicfunction())を備えています。 NOTprivatefunctionを直接呼び出します(つまり、a.privatefunction())、ただpublicfunction()です。

その最小の例ですが、多分あなたはそれの用途を見ることができますか?これを使用して、パブリック/プライベートメソッドを実施しました。

216
Francisco Soto

Webページでユーザーがボタンをクリックした回数をカウントするにしたいとします。
このために、変数のカウントを更新するためにonclickボタンのイベントで関数をトリガーしています

<button onclick="updateClickCount()">click me</button>  

現在、次のような多くのアプローチがあります。

1)グローバル変数、およびcounterを増やす関数を使用できます。

var counter = 0;

function updateClickCount() {
    ++counter;
    // do something with counter
}

しかし、落とし穴は、ページ上のどのスクリプトでも、updateClickCount()を呼び出さずにカウンターを変更できるです。


2)今、あなたは関数内で変数を宣言することを考えているかもしれません:

function updateClickCount() {
    var counter = 0;
    ++counter;
    // do something with counter
}

しかし、ねえ! updateClickCount()関数が呼び出されるたびに、カウンターが再び1に設定されます。


3)入れ子関数?について考える

ネストされた関数は、それらの「上」のスコープにアクセスできます。
この例では、内部関数updateClickCount()は、親関数countWrapper()のカウンター変数にアクセスできます

function countWrapper() {
    var counter = 0;
    function updateClickCount() {
    ++counter;
    // do something with counter
    }
    updateClickCount();    
    return counter; 
}

外部からupdateClickCount()関数にアクセスでき、counter = 0を毎回ではなく一度だけ実行する方法を見つける必要がある場合、これはカウンターのジレンマを解決できたかもしれません。


4)救助への閉鎖!(自己呼び出し機能)

 var updateClickCount=(function(){
    var counter=0;

    return function(){
     ++counter;
     // do something with counter
    }
})();

自己呼び出し関数は1回だけ実行されます。 counterをゼロ(0)に設定し、関数式を返します。

このようにupdateClickCountは関数になります。 「素晴らしい」部分は、親スコープのカウンターにアクセスできることです。

これはJavaScriptクロージャと呼ばれます。関数に「private」変数を含めることができます。

counterは匿名関数のスコープによって保護されており、add関数を使用してのみ変更できます!

クロージャのより活発な例:

<script>
        var updateClickCount=(function(){
        var counter=0;
    
        return function(){
        ++counter;
         document.getElementById("spnCount").innerHTML=counter;
        }
      })();
    </script>

    <html>
         <button onclick="updateClickCount()">click me</button>
          <div> you've clicked 
                <span id="spnCount"> 0 </span> times!
         </div>
    </html>
156
JerryGoyal

あなたが与える例は素晴らしいものです。クロージャは、懸念を非常にきれいに分離できるようにする抽象化メカニズムです。あなたの例は、インストルメンテーション(呼び出しのカウント)をセマンティクス(エラー報告API)から分離する場合です。その他の用途は次のとおりです。

  1. パラメータ化された動作をアルゴリズムに渡す(古典的な高次プログラミング):

    function proximity_sort(arr, midpoint) {
        arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; });
    }
    
  2. オブジェクト指向プログラミングのシミュレーション:

    function counter() {
        var a = 0;
        return {
            inc: function() { ++a; },
            dec: function() { --a; },
            get: function() { return a; },
            reset: function() { a = 0; }
        }
    }
    
  3. JQueryのイベント処理やAJAX AP​​Iなどのエキゾチックなフロー制御を実装します。

65
Marcelo Cantos

はい、それは便利なクロージャの良い例です。 warnUserを呼び出すと、スコープ内にcalledCount変数が作成され、warnForTamper変数に格納されている匿名関数が返されます。 calledCount変数を使用するクロージャーがまだあるため、関数の終了時に削除されないため、warnForTamper()を呼び出すたびにスコープ変数が増加し、値が警告されます。

StackOverflowで最もよく見られる問題は、誰かが各ループで増加する変数の使用を「遅延」させたい場合ですが、変数がスコープされるため、変数への各参照はループの終了後になり、結果として変数の終了状態:

for (var i = 0; i < someVar.length; i++)
    window.setTimeout(function () { 
        alert("Value of i was "+i+" when this timer was set" )
    }, 10000);

これにより、すべてのアラートが同じ値のiを表示します。これは、ループが終了したときに増加した値です。解決策は、変数の別のスコープである新しいクロージャーを作成することです。これは、変数を受け取り、その状態を引数として保存する、すぐに実行される匿名関数を使用して実行できます。

for (var i = 0; i < someVar.length; i++)
    (function (i) {
        window.setTimeout(function () { 
            alert("Value of i was "+i+" when this timer was set" )
        }, 10000);
    })(i); 
18
Andy E

私はこの質問に答えるのが非常に遅いことを知っていますが、それは2018年にまだ答えを探している人を助けるかもしれません。

JavaScriptクロージャーを使用して、アプリケーションでthrottleおよびdebounce機能を実装できます。

調整

スロットリングは、時間の経過とともに関数を呼び出すことができる最大回数として制限を設けます。 「100ミリ秒ごとに最大1回この関数を実行する」のように。

コード:

const throttle = (func, limit) => {
  let isThrottling
  return function() {
    const args = arguments
    const context = this
    if (!isThrottling) {
      func.apply(context, args)
      isThrottling = true
      setTimeout(() => isThrottling = false, limit)
    }
  }
}

デバウンス

デバウンスは、関数が呼び出されずに一定の時間が経過するまで呼び出されないように関数に制限を設定します。 「呼び出されずに100ミリ秒が経過した場合にのみ、この関数を実行します。」

コード:

const debounce = (func, delay) => {
  let debouncing
  return function() {
    const context = this
    const args = arguments
    clearTimeout(debouncing)
    debouncing = setTimeout(() => func.apply(context, args), delay)
  }
}

ご覧のとおり、クロージャーは、すべてのWebアプリがスムーズなUIエクスペリエンス機能を提供するために必要な2つの美しい機能の実装に役立ちました。

私はそれが誰かを助けることを願っています。

16

特に、JavaScript(またはECMAScript)言語では、クロージャーは機能の実装を非表示にしつつ、インターフェースを明らかにするのに役立ちます。

たとえば、日付ユーティリティメソッドのクラスを作成していて、ユーザーがインデックスで曜日名を検索できるようにしたいが、内部で使用する名前の配列を変更できないようにしたいとします。

var dateUtil = {
  weekdayShort: (function() {
    var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
    return function(x) {
      if ((x != parseInt(x)) || (x < 1) || (x > 7)) {
        throw new Error("invalid weekday number");
      }
      return days[x - 1];
    };
  }())
};

days配列はdateUtilオブジェクトのプロパティとして単純に格納できますが、スクリプトのユーザーに表示され、ソースがなくても必要に応じて変更することもできます。コード。ただし、日付ルックアップ関数を返す匿名関数に囲まれているため、ルックアップ関数からのみアクセスできるため、改ざん防止になりました。

14
maerics

Mozilla Developer NetworkPractical Closures のセクションがあります。

6
alex

クロージャーのもう1つの一般的な用途は、メソッド内のthisを特定のオブジェクトにバインドし、他の場所(イベントハンドラーなど)で呼び出せるようにすることです。

function bind(obj, method) {
    if (typeof method == 'string') {
        method = obj[method];
    }
    return function () {
        method.apply(obj, arguments);
    }
}
...
document.body.addEventListener('mousemove', bind(watcher, 'follow'), true);

Mousemoveイベントが発生するたびに、watcher.follow(evt)が呼び出されます。

クロージャは高次関数の重要な部分でもあり、異なる部分をパラメータ化することにより、複数の類似した関数を単一の高次関数として書き換える非常に一般的なパターンを可能にします。抽象的な例として、

foo_a = function (...) {A a B}
foo_b = function (...) {A b B}
foo_c = function (...) {A c B}

になる

fooer = function (x) {
    return function (...) {A x B}
}

aとBは構文単位ではなく、ソースコード文字列(文字列リテラルではありません)です。

具体的な例については、「 関数でJavaScriptを合理化する 」を参照してください。

5
outis

ここで、何度か言いたい挨拶があります。クロージャを作成する場合、その関数を呼び出して挨拶を録音するだけです。クロージャーを作成しない場合、毎回名前を渡す必要があります。

クロージャーなし( https://jsfiddle.net/lukeschlangen/pw61qrow/3/ ):

function greeting(firstName, lastName) {
  var message = "Hello " + firstName + " " + lastName + "!";
  console.log(message);
}

greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");

クロージャーあり( https://jsfiddle.net/lukeschlangen/Lb5cfve9/3/ ):

function greeting(firstName, lastName) {
  var message = "Hello " + firstName + " " + lastName + "!";

  return function() {
    console.log(message);
  }
}

var greetingBilly = greeting("Billy", "Bob");
var greetingLuke = greeting("Luke", "Schlangen");

greetingBilly();
greetingBilly();
greetingBilly();
greetingLuke();
greetingLuke();
greetingLuke();
5
Luke Schlangen

オブジェクト指向の意味でクラスをインスタンス化するという概念に慣れている(つまり、そのクラスのオブジェクトを作成する)場合は、クロージャーを理解することに近づいています。

このように考えてください。2つのPersonオブジェクトをインスタンス化すると、クラスメンバー変数「Name」がインスタンス間で共有されないことがわかります。各オブジェクトには独自の「コピー」があります。同様に、クロージャーを作成すると、自由変数(上記の例では「calledCount」)は関数の「インスタンス」にバインドされます。

WarnUser関数によって返されるすべての関数/クロージャー(それは高次関数)クロージャーが同じ初期値(0 )、クロージャーを作成するときは、クラスのコンストラクターに異なる値を渡すのと同じように、多くの場合、異なる初期化子を高階関数に渡す方が便利です。

したがって、「calledCount」が特定の値に達したときに、ユーザーのセッションを終了するとします。リクエストがローカルネットワークから送られてきたのか、それとも大規模なインターネットから送られてきたのかによって、異なる値が必要になる場合があります(そう、不自然な例です)。これを実現するには、calledCountのさまざまな初期値をwarnUserに渡します(つまり、-3、または0?)。

文献に関する問題の一部は、それらを説明するために使用される命名法です(「字句範囲」、「自由変数」)。だまされてはいけません、クロージャーは表示されるよりも単純です...一応;-)

4
EdwardGarson

JavaScriptモジュールパターンはクロージャーを使用します。その素敵なパターンにより、「パブリック」変数と「プライベート」変数を似たものにすることができます。

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };

})();
2

Mozillaの関数ファクトリが好きです

function makeAdder(x) {

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

var addFive = makeAdder(5);

console.assert(addFive(2) === 7); 
console.assert(addFive(-5) === 0);
2
Tom

ここに、Eコマースサイトや他の多くのサイトで使用できる閉鎖コンセプトの簡単な例を示します。サンプルにjsfiddleリンクを追加しています。 3つのアイテムと1つのカートカウンターの小さな製品リストが含まれています。

Jsfiddle

//Counter clouser implemented function;
var CartCouter = function(){
        var counter = 0;
  function changeCounter(val){
        counter += val
  }
  return {
        increment: function(){
        changeCounter(1);
    },
    decrement: function(){
    changeCounter(-1);
    },
    value: function(){
    return counter;
    }
  }
}

var cartCount = CartCouter();
function updateCart(){
        document.getElementById('cartcount').innerHTML = cartCount.value();
  }

var productlist = document.getElementsByClassName('item');
for(var i = 0; i< productlist.length; i++){
        productlist[i].addEventListener('click',function(){
        if(this.className.indexOf('selected')<0){
                this.className += " selected";
        cartCount.increment();
        updateCart();
    } else{
        this.className = this.className.replace("selected", "");
      cartCount.decrement();
      updateCart();
    }
  })
}
.productslist{
  padding:10px;
}
ul li{
  display: inline-block;
  padding: 5px;
  border: 1px solid #ddd;
  text-align: center;
  width: 25%;
  cursor: pointer;
}
.selected{
  background-color: #7CFEF0;
  color: #333;
}
.cartdiv{
  position: relative;
  float:right;
  padding: 5px;
  box-sizing: border-box;
  border: 1px solid #f1f1f1;
}
<div>
<h3>
Practical Use of JavaScript Closure consept/private variable.
</h3>
<div class="cartdiv">
    <span id="cartcount">0</span>
</div>
<div class="productslist">
    <ul >
    <li class="item">Product 1</li>
     <li class="item">Product 2</li>
     <li class="item">Product 3</li>
    </ul>

</div>
</div>
2
Abhilash

クロージャーの使用:

クロージャーはJavaScriptの最も強力な機能の1つです。 JavaScriptは、関数のネストを許可し、内部関数に、外部関数内で定義されたすべての変数と関数(および外部関数がアクセスできる他のすべての変数と関数)へのフルアクセスを許可します。ただし、外部関数は、内部関数内で定義された変数および関数にアクセスできません。これにより、内部関数の変数に一種のセキュリティが提供されます。また、内部関数は外部関数のスコープにアクセスできるため、内部関数が外部関数の寿命を超えて生き残った場合、外部関数で定義された変数と関数は外部関数自体よりも長く存続します。内部関数が外部関数の外部のスコープで何らかの形で利用可能になると、クロージャーが作成されます。

例:

<script>
var createPet = function(name) {
  var sex;

  return {
    setName: function(newName) {
      name = newName;
    },

    getName: function() {
      return name;
    },

    getSex: function() {
      return sex;
    },

    setSex: function(newSex) {
      if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
console.log(pet.getName());                  // Vivie

console.log(pet.setName("Oliver"));   
console.log(pet.setSex("male"));
console.log(pet.getSex());                   // male
console.log(pet.getName());                  // Oliver
</script>

上記のコードでは、外部関数の名前変数は内部関数からアクセスできます。また、内部関数を使用しない限り、内部変数にアクセスする方法はありません。内部関数の内部変数は、内部関数の安全なストアとして機能します。それらは、内部機能が動作するための「永続的」かつ安全なデータを保持します。関数は、変数に割り当てられたり、名前を持つ必要さえありません。読む こちら 詳細

2
Sunny S.M

イベント処理コードを簡素化するためにクロージャーを使用する方法についてしばらく前に記事を書きました。 ASP.NETイベント処理をクライアント側のjQueryと比較します。

http://www.hackification.com/2009/02/20/closures-simplify-event-handling-code/

1
stusmith

このスレッドは、クロージャーがどのように機能するかをよりよく理解するのに非常に役立ちました。それ以来、私はいくつかの実験を行って、クロージャーを実際的な方法で使用する方法や、異なるレベルでクロージャーを使用して静的および/またはグローバル変数が上書きされたり、グローバル変数と混同される危険がないグローバル変数。これは、各ボタンのローカルレベルとグローバルレベルの両方でボタンクリックを追跡し、すべてのボタンクリックをカウントして、1つの数字に貢献します。注:これを行うためにグローバル変数を使用したことはありません。これは、演習のポイントの1つです。グローバルに何かに貢献するボタンに適用できるハンドラーがあります。

専門家にお願いします、ここで悪い習慣を犯したかどうか教えてください!私はまだこのことを自分で学んでいます。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Closures on button presses</title>
<script type="text/javascript">

window.addEventListener("load" , function () {
    /*
    grab the function from the first closure,
    and assign to a temporary variable 
    this will set the totalButtonCount variable
    that is used to count the total of all button clicks

    */
    var buttonHandler = buttonsCount(); 

    /*
    using the result from the first closure (a function is returned) 
    assign and run the sub closure that carries the 
    individual variable for button count and assign to the click handlers 
    */
    document.getElementById("button1").addEventListener("click" , buttonHandler() );
    document.getElementById("button2").addEventListener("click" , buttonHandler() );
    document.getElementById("button3").addEventListener("click" , buttonHandler() );

    // Now that buttonHandler has served its purpose it can be deleted if needs be
    buttonHandler = null;
});



function buttonsCount() {
    /* 
        First closure level 
        - totalButtonCount acts as a sort of global counter to count any button presses
    */
    var totalButtonCount = 0;

    return  function () {
        //second closure level
        var myButtonCount = 0;

        return function (event) {
            //actual function that is called on the button click
            event.preventDefault();
            /*  
               increment the button counts.
               myButtonCount only exists in the scope that is 
               applied to each event handler, therefore acts 
               to count each button individually whereas because 
               of the first closure totalButtonCount exists at 
               the scope just outside, so maintains a sort 
               of static or global variable state 
            */

            totalButtonCount++;
            myButtonCount++;

            /* 
                do something with the values ... fairly pointless 
                but it shows that each button contributes to both 
                it's own variable and the outer variable in the 
                first closure 
            */
            console.log("Total button clicks: "+totalButtonCount);
            console.log("This button count: "+myButtonCount);
        }
    }
}

</script>
</head>

<body>
    <a href="#" id="button1">Button 1</a>
    <a href="#" id="button2">Button 2</a>
    <a href="#" id="button3">Button 3</a>
</body>
</html>
1
Darren Crabb

指定されたサンプルでは、​​囲まれた変数 'c​​ounter'の値は保護されており、指定された関数(増分、減分)を使用してのみ変更できます。閉鎖状態にあるため、

var MyCounter= function (){
    var counter=0;
    return {
        increment:function () {return counter += 1;},
        decrement:function () {return counter -= 1;},
        get:function () {return counter;}
    };
};

var x = MyCounter();
//or
var y = MyCounter();

alert(x.get());//0
alert(x.increment());//1
alert(x.increment());//2

alert(y.increment());//1
alert(x.get());// x is still 2
0
ShAkKiR

フロントエンドJavaScriptで記述するコードの多くはイベントベースです。いくつかの動作を定義してから、ユーザーがトリガーするイベント(クリックやキー押下など)にアタッチします。通常、コードはコールバックとして添付されます。イベントに応答して実行される単一の関数です。 size12、size14、およびsize16は、本文テキストをそれぞれ12、14、および16ピクセルにサイズ変更する関数になりました。次のようにボタン(この場合はリンク)に添付できます。

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

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

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

フィドル

0
Muhammad Usman

参照: クロージャーの実際の使用法

実際には、クロージャーはエレガントなデザインを作成し、さまざまな計算のカスタマイズ、遅延呼び出し、コールバック、カプセル化されたスコープの作成などを可能にします。

Sort-condition関数を引数として受け取る配列のソート方法の例:

[1, 2, 3].sort(function (a, b) {
    ... // sort conditions
});

関数引数の条件によって新しい配列をマップする配列のマップメソッドとしての関数のマッピング:

[1, 2, 3].map(function (element) {
   return element * 2;
}); // [2, 4, 6]

多くの場合、検索のほぼ無制限の条件を定義する関数引数を使用して検索関数を実装すると便利です。

 someCollection.find(function (element) {
        return element.someProperty == 'searchCondition';
    });

また、たとえば、要素の配列に関数を適用するforEachメソッドとして、関数を適用することに注意できます。

[1, 2, 3].forEach(function (element) {
    if (element % 2 != 0) {
        alert(element);
    }
}); // 1, 3

関数は、引数(適用中の引数のリスト、および呼び出し中の位置指定された引数)に適用されます。

(function () {
  alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);

遅延呼び出し:

var a = 10;
    setTimeout(function () {
      alert(a); // 10, after one second
    }, 1000);

コールバック関数:

var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
  // callback, which will be called deferral ,
  // when data will be ready;
  // variable "x" here is available,
  // regardless that context in which,
  // it was created already finished
  alert(x); // 10
};

補助オブジェクトを隠すためのカプセル化されたスコープの作成:

var foo = {};
(function (object) {
  var x = 10;
  object.getX = function _getX() {
    return x;
  };
})(foo);
alert(foo.getX());// get closured "x" – 10
0
Damodaran

クロージャは、 generators 、オンデマンドで増加するシーケンスを作成する便利な方法です。

    var foobar = function(i){var count = count || i; return function(){return ++count;}}

    baz = foobar(1);
    console.log("first call: " + baz()); //2
    console.log("second call: " + baz()); //3

違いは次のように要約されます。

匿名関数定義済み関数
 
メソッドとして使用できませんオブジェクトのメソッドとして使用できます
 
スコープ内にのみ存在します
 
定義されているスコープ内でのみ呼び出すことができますコードの任意のポイントで呼び出すことができます
 
新しい値を再割り当てまたは削除できます削除または変更できません

参考文献

0
Paul Sweatte