What is a closure? Let’s start with a definition from the Definitive JavaScript Guide:

Function objects can be linked by scope chains, and variables inside the function body can be stored in the function scope. This feature is called closures

Ha ha ha is not a face meng? It doesn’t matter. Let’s start with the simplest scope, scope chain, and explore what is a closure

1. Scope

(1) Function scope

What is scope? The scope of a variable is the area in the source code where it is defined. In JavaScript, function scope is used

That is, variables are defined in the body of the function in which they are declared and in any nested function

function scope() {
    if (true) {
        var i = 0
        for (var j = 1; j <= 5; j++) {
            i = i + j
        }
        console.log(j)
    }
    console.log(i)
    ~function() { // Execute the function immediately
        console.log(i)
    	console.log(j)
    }()
}

scope()

/* * Result: * 6 * 15 * 15 * 6 **/
Copy the code

(2) Declaration in advance

JavaScript uses function scope, which means that all variables declared inside a function are visible inside that function

This results in variables being available before they are declared, a phenomenon called declaration ahead in JavaScript, but it is important to note that assignment is not ahead of time

function hoisting() {
    console.log(i) // Declare ahead, but assignment is not ahead
    var i = 'hello'
    console.log(i)
}

hoisting()

/* * Result: * undefined * hello **/
Copy the code

2. Scope chain

(1) Declare the context object

We know that in JavaScript global variables are properties of global objects, but many people don’t know that local variables are also properties of an object

This object is called the declaration context object, and it is an object associated with the function call that, as an internal implementation, we cannot reference directly

(2) Scope chain

Each piece of JavaScript code (global code or function) has a chain of scopes associated with it

A scope chain is simply a list of objects that define the variables in the scope of this code, called variable objects

In general, variable objects include global objects that define global variables and declaration context objects that define local variables

In global code (that is, code not included in a function definition), there is only one object in the scope chain, which is the global object

For unnested functions, there are two objects on the scope chain, the first is the object that defines the function parameters and local variables, and the second is the global object

For nested functions, there are at least three objects in the scope chain (think about what they are)

(3) Variable analysis

When JavaScript needs to find the value of a variable x, it starts with the first object in the scope chain

If there is an attribute named x on this object, the value of that attribute is directly used as the value of variable x

If the object does not have an attribute named X, the next object on the scope chain is searched until the end of the scope chain is reached

If no object in the scope chain has an attribute named x, a reference exception is thrown

(4) Scope chain of function

When a function is defined, a chain of scopes is saved; When this function is called, a new object is created to hold local variables

This object is then added to the saved scope chain and a new scope chain is created to represent the scope of the function call

When the function returns, the object is removed from the saved scope chain, but this does not mean that the object is immediately garbage collected

Because of JavaScript’s garbage collection mechanism, objects are only collected by JavaScript if they are not referenced

3, closures

(1) Understanding of closures

If a function contains a nested function inside (but does not return the nested function), the nested function is also recycled when the external function returns

The core of a closure is that when a function contains a nested function and returns the nested function, there is an external reference to the nested function

The nested function is not garbage collected, and its corresponding scope chain is preserved

var global_scope = 'global'
function outer_function(outer_params) {
    var outer_scope = 'outer'
    console.log(outer_scope)
    // console.log(inner_scope) -> inner_scope is not defined
    var inner_function = function(inner_params) {
        var inner_scope = 'inner'
        console.log(outer_scope)
    	console.log(inner_scope)
    }
    return inner_function
}
outer_function('outer') ('inner')

/* * Result: * outer * outer * inner **/
Copy the code

Outer_function is called with the following scope chain:

  1. outer_function{outer_params:'outer',outer_scope:'outer',inner_function:function}
  2. window{global_scope:'global'}

Therefore, when printing outer_scope, first look for the outer_function object, which can find the outer_scope property, and return directly

To print inner_scope, look for neither the outer_function object nor the window object to find the outer_scope property, throwing an exception


When we call inner_function, the scope chain looks like this:

  1. inner_function{inner_params:'inner',inner_scope:'inner'}
  2. outer_function{outer_params:'outer',outer_scope:'outer',inner_function:function}
  3. window{global_scope:'global',outer_function:function}

Print outer_scope, the first lookup inner_function object, did not find, and then find outer_function object, can be found

When printing inner_scope, we first look for the inner_function object

(2) The application of closures

  • Defining private properties
var counter = (function() { // Execute the function immediately and return an object
    var value = 0 // Private attributes that cannot be accessed directly
    var changeBy = function(val) { value += val } // Private method, cannot be accessed directly
    // The following nested functions share a scope chain
    return {
        getValue: function() { return value },
        increase: function() { changeBy(+1)},decrease: function() { changeBy(-1)}}}) ()console.log(counter.getValue())
counter.increase()
console.log(counter.getValue())
counter.decrease()
console.log(counter.getValue())

/* * Result: * 0 * 1 * 0 **/
Copy the code
  • Caching results
function memory(f) {
    // Cache the result
    var cache = {}
    return function() {
        // Use the parameter passed as the key
        var key = arguments.length + Array.prototype.join.call(arguments.', ')
        if (key in cache) { // If the value is in the cache, read it directly
            return cache[key]
        } else { // Otherwise perform the calculation and put the result in the cache
            return cache[key] = f.apply(this.arguments)}}}var factorial = function(n) { return (n <= 1)?1 : n * factorial(n - 1)}var factorialWithMemory = memory(factorial)
Copy the code

(3) Considerations for using closures

  • Whether the value of an external variable changes
function test() {
    var array = []
    for(var count = 0; count < 5; count++) {
        array[count] = function() { console.log(count) }
    }
    return array
}

var result = test()
result[0]()
result[1]()
result[2]()
result[3]()
result[4] ()/* * Result: * 5 * 5 * 5 * 5 * 5 ** */

/* * count = 5 **/; /* count = 5 **/
Copy the code

The solution

function test() {
    var array = []
    for(var count = 0; count < 5; count++) {
        array[count] = function(value) { // Execute the function immediately
            return function() { console.log(value) }
        }(count)
    }
    return array
}

var result = test()
result[0]()
result[1]()
result[2]()
result[3]()
result[4] ()/* * Result: * 0 * 1 * 2 * 3 * 4 **/

/* * the value of the function is equal to the value of the count passed in **/
Copy the code
  • Is this pointing as expected
var test = {
	value: 0.getValue: function() {
        return function() { console.log(this.value) }
    }
}

var result = test.getValue()
result()

/*
 * 执行结果:
 * undefined
**/

/* * When the closure function is called, it is executed in global scope, so this refers to the global object **/
Copy the code

The solution

var test = {
	value: 0.getValue: function() {
        return function() { console.log(this.value) }.bind(this)}}var result = test.getValue()
result()

/*
 * 执行结果:
 * 0
**/

/* * Bind this to the test object **/
Copy the code