(Turn) Scope and Closures

https://github.com/stone0090/javascript-lessons/tree/master/2.4-Scope&Closure

scope and closure

Scope and closure are one of the most important concepts in JavaScript. If you want to learn JavaScript further, you must understand JavaScript How scopes and closures work.

Scope

Any programming language has the concept of scope. Simply put, scope is the accessible scope of variables and functions, that is, scope controls the visibility and life cycle of variables and functions. In JavaScript, there are two types of variable scopes: global scope and local scope.

Global scope (Global Scope)

Objects that can be accessed anywhere in the code have global scope. Generally speaking, the following three situations have global scope:

1. The outermost function and the definition outside the outermost function Variables have global scope, for example:

var global = "global"; // explicitly declare a global variable
function checkscope() {
    var local = "local"; // explicitly declare a local variable
    return global; // return global The value of the variable
}
console.log(global); // "global"
console.log(checkscope()); // "global"
console.log(local); // error: local is not defined.
In the above code, global is a global variable, whether it is inside or outside the checkscope() function, the global variable global can be accessed.

2. All variables that are not defined and directly assigned are automatically declared to have global scope, for example:

function checkscope() {
    var local = "local"; // Explicitly declare a local variable
    global = "global"; // Implicit declaration A global variable (bad writing)
}
console.log(global); // "global"
console.log(local); // error: local is not defined.
In the above code, the variable global is not defined with the var keyword It is directly assigned, so the global variable global is implicitly created, but this way of writing is easy to cause misunderstandings, and this way of writing should be avoided as much as possible.

3. All properties of the window object have global scope In

general , the built-in properties of the window object have global scope, such as window.name, window.location, window.top and so on.

The local scope (Local Scope)

is the opposite of the global scope. The local scope is generally only accessible within a fixed code fragment. The most common are variables defined within the body of a function, which can only be used within the body of the function. For example:

function checkscope() {
    var local = "local"; // explicitly declare a local variable
    return local; // return the value of a global variable
}
console.log(checkscope()); // "local"
console.log(local); // error : local is not defined.
In the above code, the variable local is defined in the function body, which can be accessed in the function body, but an error is reported when it is accessed outside the function.

The relationship between global and local scopes

Within a function body, local variables take precedence over global variables of the same name. If a local variable declared in a function or a variable with a function parameter has the same name as the global variable, then the global variable is overshadowed by the local variable.

var scope = "global"; // declares a global variable
function checkscope() {
    var scope = "local"; // declares a local variable with the same name
    return scope; // returns the value of the local variable, not the global variable
}
console.log(checkscope()); // "local"
Although you can write code in the global scope without a var statement, you must use a var statement when declaring a local variable. Think about what would happen if you didn't:

scope = "global";
function checkscope2() {
    scope = "local"; // Oops! We just modified the global variable
    myscope = "local"; // a new global variable is explicitly declared here
    return [scope, myscope];// returns two values
}
console.log(checkscope2()); // ["local", "local"], has a side effect
console.log(scope); // "local", the global variable modifies
console.log(myscope); // "local", the global namespace messes up the
function Definitions can be nested. Since each function has its own scope, there are several cases where local scopes are nested, for example:

var scope = "global scope"; // global variable
function checkscope() {
    var scope = "local scope "; // local variable
    function nested() {
        var scope = "nested scope"; // local variable in nested scope
        return scope;


}
console.log(checkscope()); // "nested scope"
function scope and declaration advance

In some programming languages ​​like C, each piece of code inside curly braces has its own scope, and variables are declared in They are invisible outside the code segment, we call it block scope, and there is no block scope in JavaScript. JavaScript uses function scope instead, where variables are defined in the body of the function in which they are declared, as well as in any function body within which the function body is nested.

In the code shown below, the variables i, j and k are defined in different places, they are all in the same scope, and these three variables are defined in the function body.

function test(o) {
    var i = 0; // i is defined in the entire function body
    if (typeof o == "object") {
        var j = 0; // j is defined in the function body, not just inside this code snippet
        for (var k = 0; k < 10; k++) { // k is defined inside the function body, not just inside the loop
            console.log(k); // output the number 0~9
        }
        console.log(k); // k is defined, output 10
    }
    console.log(j); // j is defined, but probably not initialized
}
Function scope in JavaScript means that all variables declared within a function are always visible within the function body. Interestingly, this means that the variable is even available before it is declared. This feature of JavaScript is informally known as hoisting, which means that all variables declared in a JavaScript function (but not involving assignment) are "advanced" to the top of the function body. Take a look at the following code:

var scope = " global";
function f() {
    console.log(scope); // prints "undefined", not "global"
    var scope = "local"; // variable is initialized here, but the variable itself is in any function body The place is defined
    console.log(scope); // output "local"
}
You may mistakenly think that the first line in the function will output "global", because the code has not yet executed to the place where the var statement declares the local variable . In fact, due to the characteristics of function scope, local variables are always defined in the entire function body, that is, the local variables in the function body cover the global variables of the same name. However, local variables are not actually assigned until the program reaches the var statement. Therefore, the above process is equivalent to: "advance" the variable declaration inside the function to the top of the function body, while the variable initialization is left in place:

function f() {
    var scope; // The local variable
    console is declared at the top of the function. log(scope); // variable exists, but its value is "undefined"

    console.log(scope); // here it has the value we expect
}
In a programming language with block-level scoping, in a narrow scope, keep the variable declaration and the code that uses the variable as close as possible to each other, usually Speaking of, this is a very good programming habit. Because JavaScript doesn't have block-level scoping, some programmers deliberately place variable declarations at the top of the function body, rather than placing the declaration close to where the variable is used. This practice makes their source code very clearly reflect the real variable scope.

Scope chain

When code is executed in an environment, it creates a scope chain of variable objects. The purpose of the scope chain is to guarantee ordered access to all variables and functions that the execution environment has access to. The front end of the scope chain is always the variable object of the environment where the currently executed code is located. If the environment is a function, use its activation object as a variable object. The active object initially contains only one variable, the arguments object (this object does not exist in the global environment). The next variable object in the scope chain is from the containing (outer) environment, and the next variable object is from the next containing environment. In this way, it continues to the global execution environment; the variable object of the global execution environment is always the last object in the scope chain.

Identifier resolution is the process of searching for identifiers one level at a time along the scope chain. The search process always starts at the front of the scope chain and works backwards step by step until an identifier is found (if the identifier is not found, an error usually occurs).

See the sample code below:

var color = "blue";

function changeColor(){
    if (color === "blue"){
        color = "red";

        color = "blue";
    }
}

console.log(changeColor());
In this simple example, the scope chain of the function changeColor() contains two objects: its own variable object (in which the arguments object is defined) and A variable object for the global environment. The variable color can be accessed inside the function because it can be found in this scope chain.

In addition, variables defined in the local scope can be used interchangeably with global variables in the local environment, as shown in this example:

var color = "blue";

function changeColor(){
    var anotherColor = "red";

    function swapColors (){
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;

        // here you can access color, anotherColor and tempColor
    }

    // here you can access color and anotherColor, but not tempColor
    swapColors();
}

// here only access color
changeColor();
The above code involves a total of 3 execution environments: the global environment, the local environment of changeColor() and the local environment of swapColors(). There is a variable color and a function changeColor() in the global environment. changeColor() has a variable called anotherColor and a function called swapColors() in its local environment, but it also has access to the variable color in the global environment. There is a variable tempColor in the local environment of swapColors(), which can only be accessed in this environment. Neither the global environment nor the local environment of changeColor() have access to tempColor. However, inside swapColors() all variables in the other two environments are accessible because those two environments are its parent execution environments. The following diagram visualizes the scope chain of the previous example.



The rectangles in the image above represent specific execution environments. Among them, the internal environment can access all external environments through the scope chain, but the external environment cannot access any variables and functions in the internal environment. The connections between these environments are linear and sequential. Every environment can search up the scope chain for variable and function names; but no environment can search down the scope chain to enter another execution environment. For swapColors() in this example, the scope chain contains three objects: the variable object of swapColors(), the variable object of changeColor(), and the global variable object. The local environment of swapColors() starts by searching for variable and function names in its own variable object, and then searches the upper-level scope chain if it cannot be found. The scope chain of changeColor() contains only two objects: its own variable object and the global variable object. That is, it cannot access the context of swapColors(). Function parameters are also treated as variables, so their access rules are the same as for other variables in the execution environment.

Closure

MDN's definition of closure:

Closures are those functions that have access to independent (free) variables (variables used locally, but defined in an enclosing scope). In other words, these functions can "memorize" the environment in which they were created.
The definition of closure in "The Definitive Guide to JavaScript (6th Edition)":

function objects can be related to each other through a scope chain, and variables inside the function body can be stored in the function scope. This feature is called in the computer science literature. Closure.
JavaScript Advanced Programming (3rd Edition) defines a closure: A

closure is a function that has access to variables in the scope of another function.
The above definitions are relatively obscure and difficult to understand. Ruan Yifeng's explanation is a little easier to understand:

since in the Javascript language, only sub-functions inside functions can read local variables, so closures can be simply understood as defined inside a function. function.
Uses of

Closures Closures can be used in many places. It has two biggest uses, one is to read the variables inside the function (scope chain), and the other is to keep the values ​​of these variables in memory all the time. How to understand this sentence? Please see the code below.

function fun() {   
    var n = 1;

    add = function() {
        n += 1
    }

    function fun2(){
        console.log(n);
    }

    return fun2;
}

var result = fun();  
result(); // 1
add();
result(); // 2
In this code, result is actually the function fun2. It runs twice, the first time with a value of 1 and the second with a value of 2. This proves that the local variable n in the function fun is always kept in memory and is not automatically cleared after fun is called.

Why is this so? The reason is that fun is the parent function of fun2, and fun2 is assigned to a global variable, which causes fun2 to always be in memory, and the existence of fun2 depends on fun, so fun is always in memory, not after the call ends. , collected by garbage collection mechanism (garbage collection).

Another thing worth noting in this code is the line add = function() { n += 1 } . First, the variable add is not preceded by the var keyword, so add is a global variable, not a local variable. Secondly, the value of add is an anonymous function, and this anonymous function itself is also a closure, which is in the same scope as fun2, so add is equivalent to a setter, which can operate on local variables inside the function outside the function .

The Counter's Dilemma

Let's take another classic example, "The Counter's Dilemma". Suppose you want to count some number, and the counter is available in all functions. You can define a global variable counter as the counter, and define an add() function to set the counter to increment. The code is as follows:

var counter = 0;
function add() {
    return counter += 1;
}

console.log(add());
console.log(add());
console.log(add());
// The counter is now 3 The
counter value changes when the add() function is executed. But here comes the problem, any script on the page can change the counter counter, even if the add() function is not called. If we define the counter inside the add() function, the value of the counter will not be arbitrarily modified by the external script. The code is as follows:

function add() {
    var counter = 0;
    return counter += 1;
}

console.log(add());
console.log(add());
console.log(add());
// original intention I wanted to output 3, but it backfired, the output is 1
because every time the add() function is called, the counter will be reset to 0, and the output is 1, which is not the result we want. Closure can just solve this problem. We define a plus() embedded function (closure) inside the add() function. The embedded function plus() can access the counter variable of the parent function. The code is as follows:

function add() {
    var counter = 0;
    var plus = function() {counter += 1;}
    plus();
    return counter;
}
Next, the counter dilemma can be solved as long as we can access the plus() function externally and ensure that counter = 0 is only executed once. The code is as follows:

var add = function() {
    var counter = 0;
    var plus = function() {return counter += 1;}
    return plus;
}

var puls2 = add();
console.log(puls2());
console .log(puls2());
console.log(puls2());
// The counter is 3
The counter is protected by the scope of the add() function and can only be modified by the puls2 method.

Notes on using closures

Since the variables in the function are stored in the memory, the memory consumption is very large, so the closure cannot be abused, otherwise it will cause performance problems of the web page, and may cause memory leaks in IE. The solution is to delete all unused local variables or set them to null before exiting the function to disconnect the variables from memory.
The closure will be outside the parent function, changing the value of the variable inside the parent function. So, if you use the parent function as an object, the closure as its public method, and the internal variable as its private value, be careful not to Feel free to change the value of the variable inside the parent function.
JavaScript closures are a powerful language feature. By using this language feature to hide variables, you can avoid overwriting variables with the same name used elsewhere, and understanding closures can help you write more efficient and concise code.

The this keyword When

it comes to scope and closures, we have to say the this keyword. Although they are not related to each other, it is easy to cause confusion when they are used together. Most of the scenarios using this are listed below to take you through.

this is a keyword in JavaScript, which refers to the context in which the function is executed, independent of the context in which the function is defined. The value of this changes depending on where the function is used. But there is a general principle that this refers to the object that called the function.

The global context

In the global context, that is, outside the body of any function, this refers to the global object.

// In browsers, this refers to the global object window
console.log(this === window); // true
function context

In function context, that is, inside any function body, this refers to the object that called the function .

this

function f1(){
    return this;
}

console.log(f1() === window); // true
As shown in the above code, directly define a function f1(), which is equivalent to defining a window object an attribute. Directly executing the function f1() is equivalent to executing window.f1(). So this in the function f1() refers to the object that called the function, which is the window object.

function f2(){
    "use strict"; // here is strict mode
    return this;
}

console.log(f2() === undefined); // true
as shown in the above code, in "strict mode", this is forbidden The keyword points to the global object (that is, the window object in the browser environment), and the value of this will remain undefined.

this

var o = {
    name: "stone",
    f: function() {
        return this.name;
    }
};

console.log(of()); // "stone"
is shown in the above code, object o Contains an attribute name and a method f(). When we execute of(), this in the method f() refers to the object that called the function, which is the object o, so this.name is also o.name.

Note that where the function is defined doesn't affect the behavior of this at all, we could also define the function first and then attach it to of . Doing this also behaves the same way. As shown in the following code:

var fun = function() {
    return this.name;
};

var o = { name: "stone" };
of = fun;

console.log(of()); // Like "stone"
, the binding of this is only affected by the closest member reference. In the example below, we call a method g() as a function of the object ob. During this execution, this in the function will point to ob. In fact, this has little to do with the members of the object itself, the closest reference is what matters.

ob = {
    name: "sophie"
    g: fun,
};

console.log(obg()); //
this

eval() method in the "sophie" eval() method can convert the string to JavaScript code, use eval () method, where does this point to? The answer is very simple, to see who is calling the eval() method, the this in the caller's execution environment is inherited by the eval() method. As shown in the following code:

// global context
function f1(){
    return eval("this");
}
console.log(f1() === window); // true

// function context
var o = {
    name: " stone",
    f: function() {
        return eval("this.
    }
};
console.log(of()); // this call() and apply() in the "stone"
call() and apply() methods are methods of the

function object, and their role is to change the calling object of the function , its first parameter represents the changed object that calls this function. Therefore, this refers to the first parameter of these two methods.

var x = 0;  
function f() {    
    console.log(this.x);  
}  
var o = {};  
ox = 1;
om = f;  
omapply(); // 0
arguments for call() and apply() When empty, the global object is called by default. Therefore, the running result at this time is 0, which proves that this refers to the global object. If the last line of code is modified to:

omapply(o); //
The result of 1 operation becomes 1, which proves that this refers to the object o at this time.

This ECMAScript 5 in the bind() method

introduces Function.prototype.bind. Calling f.bind(someObject) will create a function with the same body and scope as f, but in this new function, this will be permanently bound to the first parameter of bind, no matter how the function is called called. As shown in the following code:

function f() {
    return this.a;
}

var g = f.bind({
    a: "stone"
});
console.log(g()); // stone

var o = {
    a: 28,
    f: f,
    g: g
} ;
console.log(of(), og()); // 28,
this in stone DOM event handlers

Generally speaking, when the function uses addEventListener and is used as an event handler, its this points to the one that triggered the event element. As shown in the following code:

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
    <button id="btn" type= "button">click</button>
    <script>
        var btn = document.getElementById("

            this.style.backgroundColor = "#A5D9F3";
        }, false);
    </script>
</body>
</html>
but in IE browser, when the function uses attachEvent, it is used as an event handler, its this points to window. As shown in the following code:

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
    <button id="btn" type= "button">click</button>
    <script>
        var btn = document.getElementById("btn");
        btn.attachEvent("onclick", function(){
            console.log(this === window);






When code is called by an inline handler, its this points to the DOM element where the listener is located. As shown in the following code:

<button onclick="alert(this.tagName.toLowerCase());">
  Show this
</button>
The alert above will display the button. Note that only this in the outer code is set like this. If this is contained in an anonymous function, it is another case. As shown in the following code:

<button onclick="alert((function(){return this})());">
  Show inner this
</button>
In this case, this is contained in the anonymous function, which is equivalent to is in the global context, so it points to the window pair

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326990742&siteId=291194637