JavaScript閉鎖の秘密の生活


Cover photo by Joan Garnell on Unsplash.


導入


クローズは1960年代にさかのぼります.そして、最も現代のプログラミング言語のずっと前に、彼らは若干のプログラミング挑戦を解決する際に非常に重要な概念であることがわかりました.概念そのものの名前は、ある程度「何かを閉じる」ことについての考えをあなたに与えなければなりません.
この記事ではJavaScriptのクロージャについて説明します.まず、プログラミングのクロージャの一般的な概念とその起源から始めます.

目次


  • General concept of closures
  • Scope
  • Name binding
  • First-class functions
  • Origin of closures

  • Closures in JavaScript
  • Lexical Environment
  • Free variable
  • Funarg problem
  • Execution context
  • Scope chain

  • Uses of JavaScript closures
  • Binding Event Handlers
  • Private Instance Variables
  • Data Encapsulation
  • Functional Programming
  • Modularization
  • Advantages of closures
  • Disadvantages of closures
  • Conclusion
  • References
  • 閉鎖の一般概念

    Wikipedia defines closures このようにうまくいきます.

    In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions.


    この定義の詳細については、すべてのトピックについて説明します.

    スコープ

    In programming, scope is a region where a name binding is valid.

    結合

    This is the association of entities with identifiers.

    一等関数

    These are functions that are passed as arguments to other functions, they can be assigned to a variable and they can be returned as the value of another function.

    閉鎖の起源

    As noted at the beginning of this article, closures dates back to the 1960s, once again Wikipedia 私たちをカバー

    The concept of closures was developed in the 1960s for the mechanical evaluation of expressions in the λ-calculus and was first fully implemented in 1970 as a language feature in the PAL programming language to support lexically scoped first-class functions.

    Peter J. Landin defined the term closure in 1964 as having an environment part and a control part as used by his SECD machine for evaluating expressions. Joel Moses credits Landin with introducing the term closure to refer to a lambda expression whose open bindings (free variables) have been closed by (or bound in) the lexical environment, resulting in a closed expression, or closure. This usage was subsequently adopted by Sussman and Steele when they defined Scheme in 1975 a lexically scoped variant of LISP, and became widespread.


    上の引用では、次の点に注意してください.
  • 一等関数
  • 語彙環境
  • 自由変数
  • ファーストクラスの関数(以前に議論される)を除いて、語彙の環境と自由な変数の後ろの概念を記事の後で議論します.
    ウィキペディアからの歴史に加えて、これは以下のように言います.

    ドミトリーソシニコフ
    @ dmitrysoshnikov

    「クロージャ」が最初に発見された方法(初期のlispコンパイラで)は、今日、主にどのように今日記述されたかではありません(「機能が外で返されるとき」).「下向きfunarg問題」に特に関連があったならば、機能が「外側で返されませんでした」、しかし、ダイナミックな範囲は痛みでした.
    午後17時46分
    このつぶやきが伴うものは、我々の次の話点です.

    JavaScriptのクロージャ

    At the onset of programming with JavaScript, the concept of closures might be a difficult thing to grasp, reality is, if you've written JavaScript code before you might have used (or created) a closure without realizing it.

    Take the following code example:

    let myName = "Habdul";
    
    function getmyName() {
        let lastName = "Hazeez";
        console.log(myName + " " + lastName);
    }
    
    getmyName(); // Habdul Hazeez
    

    When the function getmyName() is called the output is Habdul Hazeez . Why is this?

    You might not know it but this is a basic example of a closure. The function getmyName() was defined with an internal variable lastName and we appended its value with the value of the variable myName defined outside the function which led to the output Habdul Hazeez when the function is invoked.

    Another question is: Why did function getmyName have access to myName variable? The answer is simple lexical environment.

    語彙環境

    From Stack Overflow :

    Lexical Environment: it's the internal JavaScript engine construct that holds identifier-variable mapping (here identifier refers to the name of the variables/functions,and variable is the reference to actual object [including function type object] or primitive value).

    A lexical environment holds a reference to a parent lexical environment.


    からもStack Overflow :

    Every function in JavaScript maintains a reference to its outer lexical environment. This reference is used to configure the execution context created when a function is invoked. This reference enables code inside the function to "see" variables declared outside the function, regardless of when and where the function is called.


    なぜ我々は機能を知っているgetmyName にアクセスできたmyName 変数.機能だからgetmyName 親の語彙環境に対する参照を持っていて、このリファレンスがこの親語彙環境で宣言された変数を見るのを可能にしたか、またはこのリファレンスが機能の外側で宣言された変数を見るために関数内のコードを有効にしたと言うことができます.
    この文脈では、関数の親の語彙環境getmyName グローバルオブジェクトと変数myName はフリー変数と呼ばれます.

    自由変数

    A free variable is a variable which is neither a parameter, nor a local variable of this function.

    Let's take a look at another example:

    let myName = "Habdul Hazeez";
    
    function logmyName() {
        console.log(myName);
    }
    
    function getmyName(funArg) {
        let myName = "Jonathan Sanchez";
        funArg();
    }
    
    getmyName(logmyName); // ?
    

    What will be the output of getmyName() and why? Let's take a step backwards and analyze what is going on.

    From the code snippet you'll notice the following:

    1. Free variable is in use ( myName ).
    2. Function logmyName is passed as an argument to function getmyName .

    In JavaScript functions are first class citizens which means we can assign them to a variable, return them from a function, and pass them as an argument to another function.

    Therefore, when we call the function getmyName as thus: getmyName(logmyName) which of the myName variable should it use? The one with the Habdul Hazeez or the one with Jonathan Sanchez ?

    This leads to a problem known as funarg problem.

    funarg問題

    The funarg problem occurs when a programming language treats functions as first class functions which has to deal with free variables.

    The funarg problem is further divided into two sub-type:

    1. downward funarg problem
    2. upward funarg problem

    We just saw the downward funarg problem in action when a free variable was declared before a function that ends up using it.

    Dmitry Soshnikov defines the downward funarg AS :

    an ambiguity at determining a correct environment of a binding: should it be an environment of the creation time, or environment of the call time?


    ゆるい意味

    When a function wants to use a variable, should it use the one present at the time it was created or the variable at the time it was called (as demonstrated by our example)?.


    この問題を解決するために、この関数は、起動時に宣言された変数を使用しますlogmyName 変数myName それが値を持って作成された場所を宣言Habdul Hazeez .
    上向きfunarg問題は次のコードスニペットで説明されています.
    function details() {
        let myName = "Habdul Hazeez";
    
        // Closure, capturing environment of `details`.
        function getmyName() {
            return myName;
        }
    
        return getmyName;
    
    }
    
    let myDetails = details();
    
    myDetails(); // Habdul Hazeez
    
    機能details ローカル変数からなるmyName と関数getmyName . 機能getmyName を返します.myName 変数.この時点で、私たちはmyName クロージャで変数が実行され、実行が完了した後にアクセスできます.
    後のコードではdetails 関数へmyDetails 変数として関数として呼び出します.すべては、関数宣言の後で起こりました.これは、キャプチャ環境(getmyName ) を作成するコンテキストを送出します(details ).

    実行コンテキスト

    In layman terms, execution context is the environment where your code is executed.

    Technically, it's more than that and the term "execution context" is a source of confusion because it's not really about a "context" but about scope.

    An execution context is created every time a function is invoked, it's composed of the activation object (the function's parameters and local variables), a reference to the scope chain, and the value of this .

    // Global context
    
    function one() {
        // "one" execution context
    
        function two() {
    
            // "two" execution context
    
        }
    
        function three() {
    
            // "three" execution context
    
        }
    
    }
    

    Every execution context created is added to the top of the execution stack. The web browser will execute the current execution context that is found at the top of the execution stack. Once completed, it will be removed from the top of the stack and control will return to the execution context below.

    Once removed, everything about the function that created the execution is destroyed, but we can preserve this state when we return an inner function which has access to the local variables, arguments, and inner function declarations of its outer function. This outer function is the parent lexical environment and the inner function is a closure.

    function getMyName() {
        let myName = "Habdul Hazeez";
    
        // inner function
        return function() {
            return myName;
        }
    
    }
    
    let whatIsMyName = getMyName();
    whatIsMyName(); // Habdul Hazeez.
    

    The variable myName is a free variable and for the inner function to search for it (before using it, in this case it simply returns it) a scope chain is used.

    スコープチェーン

    A scope chain is a list of objects that are searched for identifiers which appear in the code of the context. In general case, a scope chain is a list of all those parent variable objects, plus (in the front of scope chain) the function’s own variable/activation object ( source ).
    前の段落から、アクティベーションオブジェクトについて知っています.しかし、変数オブジェクトは何ですか?
    もう一度、救出にDmitry.He defined a variable object as thus :

    A variable object is a container of data associated with the execution context. It’s a special object that stores variables and function declarations defined in the context.


    したがって、匿名関数が変数myName ローカル変数の一部として、それを検索するスコープチェーンを使用し、変数はその関数のために作成された親変数オブジェクトで見つかりましたgetMyName .
    スコープチェーンは、以下の例に示すように深い入れ子関数を持つときにも使用されます.
    function first() {
        second();
        function second() {
            third();
            function third() {
                fourth();
                function fourth() {
                    // code here
                }
            }
        }   
    }
    
    first();
    
    The fourth 関数はグローバル変数と3つの前の関数の中で定義された変数にアクセスできる.
    単純に、関数の実行コンテキスト内で変数にアクセスしようとするたびに、ルックアッププロセスは常に独自の変数オブジェクトから開始されます.識別子が変数オブジェクトに見つからない場合、検索はスコープチェインに入ります.すべての実行コンテキストの変数オブジェクトを調べて、変数名へのマッチを探すスコープチェインを登ります.source ).
    ES 5では、変数オブジェクトの概念と活性化オブジェクトを、先に論じた語彙環境モデルに結合する.

    JavaScript閉鎖の使用

    As stated at the beginning of this article, closures do solve some programming challenges. It's impossible and impractical to cover them all, instead we'll discuss some situations that closures are really useful.

    In no particular order they are:

    • Binding Event Handlers
    • Private Instance Variables
    • Data Encapsulation
    • Functional Programming
    • Modularization

    結合イベントハンドラ

    Events occur as a result of user interaction with the application interface e.g. mouse clicks and key press.

    JavaScript is used to handle events on a Web page and there are numerous ways to track events on a Web page.

    Let's take a hypothetical example that we'll like to know which button was clicked on a Web page so that we can perform further actions after the click event.

    <button>Click me</button>
    <button>Click me1</button>
    <button>Click me2</button>
    

    Our first approach can go as thus:

    1. Select all button on the Web page.
    2. Loop through the result then attach an event listener to each button.
    var myElements = document.getElementsByTagName('button');
    
    for (var i = 0; i < myElements.length; i++) {
        myElements[i].onclick = function() {
            alert( 'You clicked on: ' + i );
        };
    }
    

    Note: We declared our variable using the var keyword just so we can see how closure was used to solve this kind of issue pre-ES6.

    When each button is clicked, the result for i is 3 which is unexpected because 3 is the last assigned value to i . This problem can be solved using closure.

    function getButton(n) {
        return function() {
            alert( 'You clicked on: ' + n );
        };
    }
    
    for (var i = 0; i < myElements.length; ++i) {
        myElements[i].onclick = getButton(i);
    }
    

    The key to understanding the modified code is that every time getButton is called, a new closure is produced, and each of these closures has a different i .

    Now, when the buttons are clicked everything works as expected.

    NOTE: In ES6 the code be rewritten as:

    let myElements = document.getElementsByTagName('button');
    
    for (let i = 0; i < myElements.length; ++i) {
        myElements[i].onclick = function() {
            alert( 'You clicked on: ' + i );
        }
    }
    

    プライベートインスタンス変数

    Functions in JavaScript can have variables declared as formal parameters and these parameters can be returned using the return keyword.

    When this function is used for creating objects with the new keyword, these variables are termed instance variables in this newly created object.

    Let's take an example that you have the following code:

    function Developer(first_name, speciality, age) {
    
       return `${first_name} ${speciality} ${age}`
    
    }
    

    The variables can be modified easily leading to undesired results.

    // Modify the variable
    Developer.first_name = "John";
    

    Now, let's construct an object from this function.

    let newDeveloper = new Developer('Ben', 'Webmaster', '100');
    

    When we check the details of newDeveloper we get an empty object due to the variable that we modified earlier.

    newDeveloper;
    // Object {  }
    

    When this object is expanded in the browser developer tools we get the following:

    {}
    <prototype>: {…}
        constructor: Developer(first_name, speciality, age)
            arguments: null
            caller: null
            first_name: "John"
            length: 3
            name: "Developer"
        prototype: {…}
        <prototype>: function ()
        <prototype>: Object { … }
    

    It's evident we've modified the variable first_name .

    What if we can prevent this happening? That's when we can use private instance variables. In reality, JavaScript has no concept of "private variables" but we can simulate it with the use of closures.

    Still using our example, we'll modify it by adding a method that will have access to the function variables, and it will prevent modification from external actors.

    function Developer(first_name, speciality, age) {
        return {
            devDetails() {
                return `${first_name} ${speciality} ${age}`
            }
        }
    }
    

    Let's repeat the steps we performed earlier.

    // Try and modify the first_name variable
    Developer.first_name = "Habdul";
    

    Construct an object from the function:

    let john = new Developer('John', 'System engineer', '34');
    

    Moving forward, we check the developer details by invoking the devDetails function and it will work as expected.

    john.devDetails(); // "John System engineer 34
    

    This was not the case when the variables were free for modification causing issues along the way. You can type the variable name john in the browser developer tools and expanding the output. It should be different compared to when we modified the first_name variable.

    データカプセル化

    Encapsulation is the process of exposing what another part of a program can access when they are divided into smaller components whereby some components are public and others are private. This includes the following:

    • Variable names
    • Functions
    • Methods (functions in an object)
    In JavaScript, encapsulation can be achieved using closures as seen in the following example from CSS-Tricks .
    const CarModule = () => {
        let milesDriven = 0;
        let speed = 0;
    
        const accelerate = (amount) => {
            speed += amount;
            milesDriven += speed;
        }
    
        const getMilesDriven = () => milesDriven;
    
        // Using the "return" keyword, you can control what gets
        // exposed and what gets hidden. In this case, we expose
        // only the accelerate() and getMilesDriven() function.
        return {
            accelerate,
            getMilesDriven
        }
    };
    

    関数型プログラミング

    Functional programming is mostly about functions. And we already know that closures can be a normal function in JavaScript or an inner function meaning we've done a bit of "functional programming" in this article. Well, let's talk about the relationship between FP (functional programming) and closures.

    In the example illustrated below we'll like to add two numbers using currying.

    Currying has its roots in mathematics and computer science and it's the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument ( source ).
    function add(a) {
    
        // The anonymous function closes over the
        // variables a and b
        return function(b) {
            return a + b;
        }
    
    }
    
    add(1)(2); //3
    
    機能add はただ一つの引数を取りますが、別の引数を取り、順番に追加の結果を返します.

    モジュール化

    Modular programming is a software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality ( source ).
    これはプログラムに含めることができる単位にコードのいくつかの行をグループ化することを含んでいます.このタイプのユニットの名前を推測できますか?関数.これらの関数は順番に別の関数を含むことができます.あなたは、私がどこに行っているかについて見ます?ボトムライン閉鎖.私たちが既に学んだのは、別の関数の中の一つの関数や関数である.
    モジュールのJavaScriptコードを書く例は次の例ですStack Overflow .
    let namespace = {};
    
    // All implementation details are in an Immediately
    // Invoked Function Expression (IIFE)
    (function foo(n) {
    
        let numbers = []
    
        function format(n) {
            return Math.trunc(n)
        }
    
        // Capture the numbers variable in a closure
        function tick() {
            numbers.push(Math.random() * 100)
        }
    
        // Capture the format function in a closure
        function toString() {
            return numbers.map(format)
        }
    
        // Expose the tick and toString function
        // to the public
        n.counter = {
            tick,
            toString
        }
    
    }(namespace))
    
    // Assign the public methods to a variable
    const counter = namespace.counter;
    
    /**
     * Invoke the tick function twice which automatically pushes
     * a random number into the private variable
     * numbers.
     */
    counter.tick();
    counter.tick();
    
    // Invoke the toString method
    console.log(counter.toString()); // Example output: Array [ 42, 46 ]
    
    その機能が明らかであるtick and toString 変数の状態を取得するnumbers ) と関数format ).

    閉鎖の利点

    The uses we've discussed so far.

    閉鎖の欠点

    Closures are useful, but they also have their disadvantages. They are:

    • As long as closures are active, this memory cannot be garbage collected.
    • Creating functions inside other functions lead to duplication in memory, potentially slowing down the application.

    結論

    In this article we've talked about JavaScript closures, but we did not cover some of its deep technical details therefore, I'll encourage you to have a look at additional literature's in the references.

    参考文献

  • Closure on Wikipedia
  • History of Closure
  • How do JavaScript closures work?
  • Implementing Private Variables In JavaScript
  • JavaScript. The Core.
  • JavaScript. The Core: 2nd Edition
  • ECMA-262-3 in detail. Chapter 4. Scope chain.
  • ECMA-262-3 in detail. Chapter 6. Closures.
  • Modular Programming by Kenneth Leroy Busbee and Dave Braunschweig
  • JavaScript closure advantages?
  • Understanding Scope and Context in JavaScript
  • What is the 'Execution Context' in JavaScript exactly?
  • Using JavaScript closures in setTimeout
  • What is the Execution Context & Stack in JavaScript?
  • 2020年9月11日更新:文法修正.