Understanding Scope in JavaScript

introduce

JavaScript has a feature called scope ( Scope). For many novice developers, the concept of scope is not so easy to understand, here I will try my best to explain to you what it is. Understanding scope makes your code better, less buggy, and through it you can make powerful design patterns.

What is scope Scope?

Scope is the accessibility ( ) Scopeof variables ( variable), functions ( function), and objects ( object) in your code at runtime ( ). In other words, scope determines whether variables and other resources are visible within a specific area of ​​your code.runtimeaccessibilityScope

Why use scope Scope: The principle of least access

Why limit the visibility of variables in code instead of making everything available everywhere? One benefit is that scopes Scopeprovide a level of security for your code. A common principle of computer security is that in each operation the user should only access what he needs for that access.

Scope in JavaScriptScope

In the JavaScript language, there are two types of scopes Scope:

  • global scope ( Global Scope)
  • local scope ( Local Scope)

Variables defined inside a function are in the local scope, while variables defined outside a function are in the global scope. Each invocation of each function creates a new scope.

global scopeGlobal Scope

When you open a document ( document) and start writing JavaScript code, you are already in the global Global Scopescope . There is only one global scope in the entire JavaScript document Global Scope. If a variable is defined outside any function, then it belongs to the global scope Global Scope. As shown in the following example:

// 这是一个JavaScipt文档的内容,而不是某个JavaScript函数定义的内容
// 一个JavaScript文档的缺省作用域是全局作用域(Global Scope)
var name = 'Hammad';

Variables defined in the global Global Scopescope can be accessed or modified in any other scope.

// 全局作用域变量定义
var name = '全局变量';

console.log(name); // 控制台输出'全局变量',全局作用域中访问全局作用域变量

function logName() {
    console.log(name); // 函数本地作用域中访问全局作用域变量
}

logName(); // 控制台输出 '全局变量'

local scopeLocal Scope

Variables defined within a function belong to the local scope Local Scope, and the local scope of these variables Local Scopeis . This means that variables with the same name can be used in different functions. This is because these variables are bound to their respective functions, each with a different scope and not accessible from other functions.

// 全局作用域 Global Scope

function someFunction() {
    // 本地作用域 Local Scope #1
    function someOtherFunction() {
        // 本地作用域 Local Scope #2
    }
}

// 全局作用域 Global Scope

function anotherFunction() {
    // 本地作用域 Local Scope #3
}
// 全局作用域 Global Scope

statement blockBlock Statements

Before ES6, blocks like if/ switchconditions, or 'for'/ whileloops Block Statements, unlike functions, did not create new scope. Variables defined within a statement block remain in the scope of the statement block.

if (true) {
    // 该语句块不产生新的作用域
    var name = '局部变量'; // 这里通过 var定义的变量name的作用域和当前所在if语句块所属的作用域相同
}

console.log(name); // '局部变量'

And since ES6, the keyword letsum was introduced const. These keywords are also used to define variables/constants and can be used instead of varkeywords. However, these two keywords varare different in that they support declaration of local scope Block Statementswithin . example:

if (true) {
    // 变量 name 通过 var 定义,
    // 所以属于当前if语句块所从属的作用域
    var name = 'var变量';//该变量的作用域和当前if语句所属作用域一样
    // likes 通过 let 定义,
    // 它属于当前if语句块内新建的一个块级作用域,和当前if语句块所属的作用域不同
    let likes = '变量,属于当前语句块内的块级作用域';
    // skills 通过 const 定义,
    // 它属于当前if语句块内新建的一个块级作用域,和当前if语句块所属的作用域不同
    const skills = '常量,和上面的变量likes的作用域一样,和上面name的作用域不同';
}

console.log(name); // 'var变量'
console.log(likes); // ReferenceError: likes is not defined
console.log(skills); // ReferenceError: skills is not defined

The global scope exists as long as your application is alive.
As long as your function is being called and executed, its local scope still exists.

contextContext

Many developers often confuse scope Scopeand context Contextas if they were referring to the same concept. But that's not the case. Scope is the concept we talked about above, and context is used to represent thisthe value pointed to at some specific place in the code. We can change the context by means of functions, which we will talk about later. In the global scope, the context is always the current Windowobject.

// 这里会输出当前Window,因为此时this指向当前Window
console.log(this);

// 定义一个函数输出this
function logFunction() {
    console.log(this);
}

// 这个函数在这里的调用还是会输出当前Window,
// 因为此时该函数不属于任何一个对象
logFunction(); 

If the scope is inside a method of an object, the context is the object the method belongs to:

class User {
    logName() {
        console.log(this);
    }
}

(new User).logName(); // 输出当前所新建的User对象 : User {}

One thing to note here is that if you newcall your function with a keyword, the context will be different from the above. The context is then set to the instance of the function being called. Reconsider our example above, this time using the newkeyword to call the function:

function logFunction() {
    console.log(this);
}
// 输出 logFunction {},
// 注意 : 这里不再输出 Window 对象了,原因 : 使用了 new 关键字
new logFunction(); 

When a function is called in Strict Mode , the context defaults to undefined.

execution contextExecution Context

To clear up all the confusion above what we've studied above, let's make a point: the word context in execution context refers to scope, not context. It's an odd name, but that's what the JavaScript specification does, and that's all we can do.

JavaScript is a single-threaded language, so only one task is executing at a time. Other tasks are queued for execution in the "execution context". As I said above, when the JavaScript interpreter starts executing your code, the context (scope) is set to global by default. This global context is appended to your execution context, which is actually the first context that started the execution context.
Then, each function call appends its own context to the execution context. The same thing happens when another function is called inside that function or somewhere else.

Each function creates its own execution context.

Once the browser completes the code in the context, the context is popped from the execution context, and the current context state in the execution context goes to the parent context. The browser always executes the execution context at the top of the execution stack (actually, always the innermost scope in your code).

There is always only one global context, and any number of function contexts.

The execution context has two phases: creation and code execution.

create stage

The first phase of an execution context is the creation phase, which occurs when a function is called but its code has not yet been executed. The main things that happen in this phase are:
- create variable object Variable(Activation) Object
- create scope chain Scope chain
- set context pointerthis

variable objectVariable Object

Variable objects, also called activation objects, contain all the variables, functions, or other things defined in a particular branch of the execution context. When a function is called, the interpreter scans all resources, including function parameters, variable definitions, and other declarations. All these things, packaged into an object, become "variable objects".

'variableObject': {
    // 包括函数参数,内部定义的变量和函数声明
}

scope chainScope Chain

During the creation phase of the execution context, the scope chain is created after the variable object is created. The scope chain itself contains variable objects. The scope chain is used to resolve( resolve) variables. When asked to resolve a variable, JavaScript always starts at the innermost level of code nesting and skips through the parent scope until the target variable or resource is found. A scope chain can simply be defined as an object that contains its own execution context variable object, but also all other parent execution contexts, which is like an object with a bunch of other objects.

'scopeChain': {
    // contains its own variable object 
    // and other variable objects of the parent execution contexts
}

execution context object

The execution context can be represented abstractly as the following objects:

executionContextObject = {
    // contains its own variableObject 
    // and other variableObject of the parent execution contexts
    'scopeChain': {}, 

    // contains function arguments, inner variable and function declarations   
    'variableObject': {}, 

    'this': valueOfThis
}

execution phase

The second phase of the execution context is the code execution phase, where the code inside the function body will eventually be executed.

lexical scopeLexical Scope

Lexical scope refers to a set of nested functions, within which functions can access variables and other resources in their parent's scope. This means that the subfunction is lexically bound to the parent's execution context. Lexical scope is sometimes called static scope. See an example:

function grandfather() {
    var name = 'Hammad';
    // 这里不能访问 likes
    function parent() {
        // 这里可以访问 name
        //  这里不能访问 likes
        function child() {
            // 作用域链的最内层
            // 这里可以访问 name
            var likes = 'Coding';
        }
    }
}

Here you will notice that with regard to lexical scoping, it works forward, that is, a variable namecan be accessed by its child execution context. But it doesn't work backwards to its parent, that is, the variable likescannot be accessed by its parent context. This also tells us that the priority order of variables with the same name in different execution contexts is from the top of the execution stack to the bottom of the stack. If a variable has the same name as another variable (annotation: defined in a different function), the one in the innermost function (the context at the top of the execution stack) will have the highest priority.

ClosureClosure

The concept of closure is closely related to lexical scope, which we have already talked about above. A closure is created when an inner function tries to access a variable outside its outer function scope chain, that is, outside the immediate lexical scope. A closure has its own scope chain, its parent scope chain and the global scope.

A closure can not only access variables defined in its outer function, but also the parameters of its outer function.

A closure can access the variables of its outer function even after the function has returned ( return). This allows the returning function to continue to access all the resources of the outer function.

When you return an internally defined function from a function, if you call the function, the returned internal function will not be called. You must first save the call to the external function in a variable and then call the variable as a function to call the function defined inside. Take a look at this example :

function greet() {
    name = 'Hammad';
    return function () {
        console.log('Hi ' + name);
    }
}

greet(); // 什么都不会发生,控制台也不输出任何东西

// greet() 被执行,它执行返回的函数被记录到了变量 greetLetter
greetLetter = greet();

 // 现在 greetLetter 就是 greet() 函数内部定义并返回的那个函数,
 // 将 greetLetter 作为函数调用将会输出 'Hi Hammad'
greetLetter(); // 输出 'Hi Hammad'

The key point to note here is that this greetLetterfunction has access to the function greet's variables while the function greethas returned.

There is also a way to call the returned function without assigning an argument and calling it directly greet, and that is to use parentheses ()twice, like this:

function greet() {
    name = 'Hammad';
    return function () {
        console.log('Hi ' + name);
    }
}

greet()(); // 输出 'Hi Hammad'

public and private scopesPublic and Private Scope

In many other languages, you can use scopes such as public, , protected, privateetc. to set the visibility of class properties. Consider the following PHP example:

// Public Scope
public $property;
public function method() {
  // ...
}

// Private Sccpe
private $property;
private function method() {
  // ...
}

// Protected Scope
protected $property;
protected function method() {
  // ...
}

Encapsulating public or global scoped functions makes them immune to attacks. But in JavaScript, there is no publicsuch privatescope. However, we can simulate this trait via closures. To separate everything from the global scope, we first wrap our function into a function like this:

(function () {
  // private scope,模拟了一个私有作用域
})();

The parentheses at the end of the function in the example above ()tell the interpreter to execute the function as soon as it reads it. We can add variables or functions inside this function and they are not accessible from outside. But what if we want to access them from the outside? That is, we need to make some of them public publicand some of them private private. Module PatternWe can do this using a type of closure called the module pattern ( ): there publiccan be both privatevisibility and visibility within an object.

module modeModule Pattern

The module pattern looks like this:

var Module = (function() {
    // 私有方法
    function privateMethod() {
        // do something
    }

    return {
        // 外部可访问方法
        publicMethod: function() {
            // can call privateMethod();
        }
    };
})();

The module Module's return statement contains our public function. Private functions are those internally defined functions that are not returned. Not returning a function is to make the function Moduledisappear from the module's namespace and become inaccessible. But our public functions can still access these private functions, which may be some helper functions that make public functions convenient, AJAX call functions, or something else.

Module.publicMethod(); // 正常可工作
Module.privateMethod(); // ReferenceError: privateMethod is not defined

One convention is that private functions use function names starting with an underscore, while objects containing public methods are returned anonymously. This makes it easier to manage in a longer object. Something like this:

var Module = (function () {
    function _privateMethod() {
        // do something
    }
    function publicMethod() {
        // do something
    }
    return {
        publicMethod: publicMethod,
    }
})();

Immediately call function expressionIIFE

Another closure type is the Immediately-Invoked Function Expression IIFE. This is an anonymous self-invoking function that uses window as the context, which means that this function thisis set to window. This exposes a single global interface for interaction. Here's how it works:

// 一个匿名自调用函数,形式参数是 window, 
// 在JavaScript文档最外层执行这段代码时,实际参数 this 其实就是当前的 window 对象
(function(window) {
    // do anything
})(this);

Use .call(), .apply() and .bind() to change context

function calland applyis used to change its context when a function is called. This provides incredible programming power. To use calland apply, you need to call them on the target function (and the first argument must be the context to use), instead of ()calling the target function directly using the method. something like this:

function hello() {
    // do something...
}

hello(); // 通常的函数调用方式
hello.call(context); // 现在 hello 函数内部的 this 是这里的 context
hello.apply(context); // 现在 hello 函数内部的 this 是这里的 context

.call()The difference between and .apply()is that, except for the first context parameter, .call()the other parameters need to be written out one by one, while .apply()the other parameters are passed through an array, see the following example:

function introduce(name, interest) {
    console.log('Hi! I\'m '+ name +' and I like '+ interest +'.');
    console.log('The value of this is '+ this +'.')
}

// 通常的函数调用方式
introduce('Hammad', 'Coding'); 
// 列出每个参数
introduce.call(window, 'Batman', 'to save Gotham'); 
// 上下文之外的参数使用数组方式传递(这个例子里面上下文传递了字符串 Hi)
introduce.apply('Hi', ['Bruce Wayne', 'businesses']); 

// Output:
// Hi! I'm Hammad and I like Coding.
// The value of this is [object Window].
// Hi! I'm Batman and I like to save Gotham.
// The value of this is [object Window].
// Hi! I'm Bruce Wayne and I like businesses.
// The value of this is Hi.

In terms of performance, it callis applyslightly faster than .

Let's look at an example where an anonymous function acts on a set of <li>elements in the document by changing the context and outputs them to the console one by one:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Things to learn</title>
</head>
<body>
    <h1>Things to Learn to Rule the World</h1>
    <ul>
        <li>Learn PHP</li>
        <li>Learn Laravel</li>
        <li>Learn JavaScript</li>
        <li>Learn VueJS</li>
        <li>Learn CLI</li>
        <li>Learn Git</li>
        <li>Learn Astral Projection</li>
    </ul>
    <script>
        // Saves a NodeList of all list items on the page in listItems
        var listItems = document.querySelectorAll('ul li');
        // Loops through each of the Node in the listItems NodeList and logs its content
        for (var i = 0; i < listItems.length; i++) {
          (function () {
            console.log(this.innerHTML);
          }).call(listItems[i]);
        }

        // Output logs:
        // Learn PHP
        // Learn Laravel
        // Learn JavaScript
        // Learn VueJS
        // Learn CLI
        // Learn Git
        // Learn Astral Projection
    </script>
</body>
</html>

This HTML document contains only an unordered set <li>. JavaScript takes them all from DOM, and then loops through each element in the list. Inside the loop, we <li>output the contents of each element to the console.

The output statement is encapsulated in an anonymous function and then the callmethod is called on it. The passed context is the current <li>element, so the inside of the function thisis the current <li>element, so that the console output statement can correctly output the content of each <li>element innerHTML.

Objects can have methods, and similarly, functions are also objects and can have methods. In fact, a JavaScript function comes with four built-in methods:

  • Function.prototype.apply()
  • Function.prototype.bind() (Introduced in ECMAScript 5 (ES5))
  • Function.prototype.call()
  • Function.prototype.toString()

Function.prototype.toString() returns a string representing the source code of the function.

So far, we've discussed .call(), .apply(), and toString(). Unlike .call()and .apply(), another function .bind()does not call the function itself, but is only used to bind the function's context value and some other parameters before the function is called. Here is a use .bind()的例子:

// 该例子都采用浏览器JavaScript环境,如果是nodejs环境,将window换成global
(function introduce(name, interest) {
    console.log('Hi! I\'m '+ name +' and I like '+ interest +'.');
    console.log('The value of this is '+ this +'.')
}).bind(window, 'Hammad', 'Cosmology')();

// 控制台输出:
// Hi! I'm Hammad and I like Cosmology.
// The value of this is [object Window].

bindSimilarly call, out-of-context parameter passing needs to be listed one by one instead of applypassing an array like that.

References

English original: Understanding Scope in JavaScript
Related article: Understanding Scope and Context in JavaScript

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325591491&siteId=291194637