The profile

This week I looked at chapter 7 of JavaScript Advanced Programming, which covered some of the features of first-class citizen-functions in JavaScript, and summarized them here.

In a function return to another function, when we call this function, we call call this function is the external function, the returned function is internal function, but the problem is coming, normal circumstances, at the end of a function call, the internal variables defined will be clean, but if the function returns the function, The returned function refers to variables defined in the external function, and those variables are somehow saved for reference by the internal function, in which case a function closure is formed

You may not understand the purpose of this, right? In fact, closure functions hide a lot of the internal implementation so that callers don’t have to worry about anything extra, and are frequently used in some cases. As mentioned earlier, JavaScript didn’t have classes, OOP basically relied on functions, and closure functions have many uses in OOP. Of course, it has some disadvantages, we need to pay attention to when using it.


Function expressions and function definitions

Function definitions are supported in most languages. If you define a function in JavaScript, you can write it like this:

function func() {
    console.log("this is a function declaration");
}

func(); // this is a function declaration
Copy the code

It is important to note that the function name cannot be omitted, and the definition is not affected before or after the call.

A function expression is different from a function definition. First, it is an expression that assigns an anonymous function (where a name is not an error, but the name is invalid) to a variable that is equivalent to the function name:

const func = function () {
    console.log("this is a function expression");
}

func(); // this is a function expression
Copy the code

Unlike before, if it is written as a function expression, then the call must be after the assignment, otherwise an error will be reported.

Function assignment expressions are not available in many compiled languages, such as Java. They do provide many advantages, such as parameter passing, multiple function entries, and so on. However, they also have some disadvantages, such as the following example:

function factorial (num) {
    if (num == 1) {
        return 1;
    }
    
    return factorial(num - 1) * num;
}

let copyOfFactorial = factorial;
factorial = null;
copyOfFactorial(10); // error
Copy the code

In this recursive implementation, the variables inside the function call are set to null externally, resulting in an error. The solution is to use arguments.callee(num -1) when calling itself internally. The alternative, of course, is to define and assign functions inside parentheses:

let copyOfFactorial = (function factorial (num) {
    if (num == 1) {
        return 1;
    }
    
    return factorial(num - 1) * num;
});
factorial = null;
copyOfFactorial(10); / / 3628800
Copy the code


Closure formation

When you’re learning some programming languages, you’ll hear more or less the term domain, a variable can’t be accessed outside of its domain, for example, if you define a variable inside a function, you can’t access it outside of the function, otherwise you’ll get an error or it won’t exist. But conversely, it is possible to access external variables from within a function. For example, if a function is defined inside a function, then the internal function can also access variables defined in the external function. All of this is understandable, but if you put it in a closure function, you might not be so sure about “why does the function return access the previous variable after the call? What’s the implementation mechanism?” The concept of a scope chain is introduced here. Think of the prototype chain before, you should not be difficult to understand, is to find a variable, first in the local domain, then go to the outer function domain, then the outer layer of the outer layer,… And finally, the big picture. The returned closure will store the variables in the scope in a cascading manner, and if they cannot be found in this layer, they will go to the next layer to look for the global, which in JS refers to the object window.

Note that closures preserve the environment and do not simply hold a variable, as in the following example:

function createFunctions(){ 
  let result = new Array(a);for (var i = 0; i < 10; i++){ 
    result[i] = function() {
        return i;
    };
  }
  
  return result;
}

const result = createFunctions();

console.log(result[8] ());/ / 10
Copy the code

This is a good example of how closures store the value of a variable at a given time, rather than just the value of a variable at a given time. A slight change to the above function would give us the desired result:

function createFunctions(){ 
  let result = new Array(a);for (var i = 0; i < 10; i++){ 
    result[i] = function(num){
        return function() {
            return num;
        }
    }(i);
  }
  
  return result;
}

const result = createFunctions();

console.log(result[8] ());/ / 8
Copy the code

Closures are a clever implementation, but it is recommended not to use them in general because, as you can see from the above analysis, closures use a lot more resources than normal functions. Of course, they can be used in appropriate situations, but it should be considered in context.


This keyword

First, let’s recall that in OOP, when JavaScript builds objects from functions, this refers to the current object. But don’t assume that in all functions or objects this refers to the current object. For global functions (except functions defined in object), in strict mode, this refers to the window object, This is undefined.

const name = "The window";
const object = {
    name : "My Object".getNameFunc : function() {
        return function () {
            return this.name; }}}console.log(object.getNameFunc()()); // The window
Copy the code

Arguments () when a function is called, it comes with two variables: this and arguments. Arguments are used to store the arguments passed in to the function. In the above example, the returned anonymous function does not belong to an object, so the returned this will point to the global window object in nonstrict mode. If you want it to point to the current external object, you only need to change it a little:

const name = "The window";
const object = {
    name : "My Object".getNameFunc : function() {
        const that = this;
        return function () {
            returnthat.name; }}}console.log(object.getNameFunc()()); // My Object
Copy the code

As you can see, if an inner function wants to retrieve information from this or arguments, we need to pass the values of these two variables to another variable, which can then be stored via a function closure. There is another tricky thing about this in the object:

const name = "the window";

const object = {
    name : "my object".getName : function() {
        return this.name; }};console.log(object.getName());      // my object
console.log((object.getName)());    // my object
console.log((object.getName = object.getName)()); // the window
Copy the code

By calling a function object behind the front two should be well understood, directly through the object calls the function, the function of this is to refer to the current object, but the third statement, parentheses object function to their first assignment, because this is follow function, if the assignment again, this will also be updated, It is not saved, so this is updated to point to the global Window object.


Memory leaks caused by closures

Let’s start with the following code:

function assignHandler() {
    const element = document.getElementById("someElement");
    element.onclick = function() {
        alert(element.id);
    };
}
Copy the code

We create an event on the element in the form of a closure. When we call assignHandler, we use element.onclick, which is the closure. This closure will hold the context of the external functions assignHandler, and you will notice that there is a reference to each other. The anonymous functions in the Element reference the element, and the anonymous functions reference the Element. As long as the closure exists, the Element variables in assignHandler cannot be detected and cleaned up by the JS garbage collector, resulting in memory leaks. Good practices are as follows:

function assignHandler() {
    const element = document.getElementById("someElement");
    const id = element.id;
    
    element.onclick = function() {
        alert(id);
    };
    
    element = null;
}
Copy the code

It is also important to note that the closure has a reference to the entire external function context. When element is not needed, we still need to set it to null and let the garbage collector clean it up


Simulation block domain

Java and C++ have such a thing as a block domain, the area inside {}, but there is no such thing as a block domain in JavaScript, which is basically divided by functions, as in the following example:

function outputNumbers(count) {
  for (var i = 0; i < count; i++) {
    console.log(i);
  }
  
  var i;
  console.log(i); / / 10
}

outputNumbers(10);
Copy the code

JavaScript doesn’t tell you where and if you’ve defined this variable. We can use anonymous functions to simulate the block domain:

function outputNumbers(count) {(function() {
        for (var i = 0; i < count; ++i) {
            console.log(i);
        }
    })();
    
    console.log(i); // error
}
Copy the code

Function () {// block code}) instead of {}, the functions in parentheses are anonymous, meaning that even if there is a closure, the closure does not store information about the anonymous function, so it is not harmful to use it. After I tried it, I realized that since ES5 introduced let and const, if I had replaced the var in the original for loop with let, I would not have been called outside the for loop. But I learned a lot when I saw my predecessors thinking of using functions to solve this problem, and it also shows the importance of functions in JavaScript.


Constructor private member

As mentioned earlier, we can create objects using constructors, but a JavaScript constructor is just a constructor. It is not bound to a class, and we cannot directly assign access to members of a function, but we can do this indirectly through closures:

function MyObject(){
  // private variables and functions 
  var privateVariable = 10;
  function privateFunction(){ 
    return false;
  }
  
  // privileged methods 
  this.publicMethod = function (){
    privateVariable++;
    return privateFunction();
  };
}
Copy the code

We define a MyObject constructor. When we use this constructor to create an object, two members, privateVariable and privateFunction(), are not members of the object because they are not referred to by this. PrivateVariable and privateFunction() can be accessed via closures within the object’s members, publicMethod. This allows us to say that private variables are public to internal members and private to objects.

We can also define it in block scope:

(function() {
  let privateVariable = 10;
  
  function privateFunction() {
    return false;
  }
  
  MyObject = function() {};
  
  MyObject.prototype.publicMethod = function() {
    privateVariable++;
    return privateFunction();
  }
})();
Copy the code

Here, we define MyObject inside an anonymous function, but notice that the MyObject variable is not declared by var, let, or const. In this case, MyObject becomes a global variable. This error is reported if it is in strict mode. It is also important to note that when you create objects in this way, when you create multiple objects, these objects have private members in the anonymous function using closed envelopes, and changes made by one object to these members affect other objects.


Block pattern

There is a design pattern called singleton. For languages like Java and C++, singleton means that a class generates only one object, which can be very efficient when used in the right context. In traditional JS, it is very simple to implement the singleton pattern, using the form of object assignment directly:

const singleton = {
    name:... .method: function() {... }};Copy the code

But with the block scope mentioned earlier you can use closures to increase access to private members of instances returned in singleton mode:

let singleton = function(){
    //private variables and functions 
    const privateVariable = 10;
    function privateFunction() { 
        return false;
    }
    
    //privileged/public methods and properties 
    return {
        publicProperty: true.publicMethod : function(){
            privateVariable++;
            return privateFunction();
        }
    };
}();
Copy the code

Here you can view the members defined in anonymous functions as private members, and the objects returned by object assignment as public members. The anonymous function is called only once, and the result is returned to the singleton variable, which can be passed and used multiple times.

The above implementation returns the parameter as an Object assignment, which creates an Object instance. There is another implementation of block mode:

var singleton = function(){
    var privateVariable = 10;
    function privateFunction(){ 
        return false;
    }

    var object = new Person();
    
    object.publicProperty = true;
    object.publicMethod = function(){ 
        privateVariable++;
        return privateFunction();
    };
    
    returnobject; } ();Copy the code

The principle is the same, except that this method can return a specific Object, not just an Object.


conclusion

So all of this stuff is around closures and functions, and functions are first-class citizens of JavaScript, and they’re very flexible and versatile, and they can be passed as values and parameters, they can be returned as values, they can be returned as classes, It can also be used to generate block domains that are not found in JavaScript. A closure means that when a function returns as an argument, the function records variables in its outer domain. With a closure, functions are enhanced and private members can be implemented indirectly. As someone who has been using Java for years, I can’t help but marvel at how functions can be played this way.