The scope and context (contexts) in JavaScript are unique to the language, thanks in part to the flexibility they bring. Each function has a different context and scope of variables. These concepts are backed by some powerful design patterns in JavaScript. However, this also brings a lot of confusion to the developers. The following is a complete revelation of the context and scope in JavaScript and how various design patterns use them.
Contexts (context) and scopes (scope)
The first thing you need to know is that the context and scope are two completely different concepts. Over the years, I've found that many developers confuse the two concepts (including myself) and mistakenly confuse the two concepts. In all fairness, many of the terms have been used in disarray over the years.
Each invocation of a function has a closely related scope and context. Fundamentally, scopes are based on functions, and contexts are object-based. In other words, the scope involves access to variables in the called function, and different invocation scenarios are not the same. The context is always the value of the This keyword, which is a reference to the object that owns (controls) the currently executing code.
Variable scope
A variable can be defined in a local or global scope, which establishes a different scope for the accessibility of variables during runtime (runtime). Any defined global variable means that it needs to be declared outside of the body of the function and that it survives the entire runtime (runtime) and can be accessed in any scope. Before ES6, local variables can exist only in the body of the function, and each invocation of a function has a different scope. Local variables can only be assigned, retrieved, manipulated within the scope of their invoked period.
Note that JavaScript does not support block-level scopes until ES6, which means block-level scopes cannot be supported in an if statement, a switch statement, a for loop, a while loop. That is, JavaScript before ES6 cannot build a block-level scope that is similar to Java (a variable cannot be accessed outside of a statement block). But, starting with ES6, you can define variables with the Let keyword, which corrects the VAR keyword, allows you to define variables like the Java language, and supports block-level scopes. See two examples:
Before ES6, we used the VAR keyword to define variables:
function func () {
if (true) {
var tmp = 123;
}
Console.log (TMP); 123
}
is accessible because the var keyword declares a variable that has a variable elevation process. In ES6 scenarios, it is recommended to use the LET keyword to define variables:
function func () {
if (true) {let
tmp = 123;
}
Console.log (TMP); REFERENCEERROR:TMP is not defined
}
This way, can avoid many mistakes.
What is the This context
The context usually depends on how the function is invoked. When a function is invoked as a method in an object, this is set to the object that invokes the method:
var obj = {
foo:function () {
alert (this = = obj);
}
;
Obj.foo (); True
This guideline also applies when you use the new operator to create an instance of an object when calling a function. In this case, the value of this within the scope of the function is set to the newly created instance:
function foo () {
alert (this);
}
New Foo ()//foo
foo ()//window
When you call a bound function, this is the global context by default, and it points to the Window object in the browser. It should be noted that ES5 introduces the concept of strict schemas, and if strict mode is enabled, then the context defaults to undefined.
Execution environment (execution context)
JavaScript is a single-threaded language, meaning that only one task can be performed at the same time. When the JavaScript interpreter initializes the execution code, it first defaults to the Global Execution environment (execution context), and from this point on, each invocation of the function creates a new execution environment.
This often leads to novice confusion, where a new term, the execution environment (execution context), defines the other data that a variable or function has access to, and determines their behavior. It is more about the role of the scope than the context we discussed earlier. It is important to distinguish carefully between the two concepts of execution environment and context (note: English is confusing). To be honest, it's a very bad naming convention, but it's a ECMAScript spec, you keep it.
Each function has its own execution environment. When the execution stream enters a function, the environment of the function is pushed into an environment stack (execution stack). After the function is finished, the stack pops up its environment and returns control to the previous execution environment. The execution flow in the ECMAScript program is controlled by this convenient mechanism.
The execution environment can be divided into two phases: creation and execution. At the creation stage, the parser first creates a variable object (variable object, also known as the active object activation objects), which consists of variables, function declarations, and parameters defined in the execution environment. At this stage, the scope chain is initialized, and the value of this is finally determined. In the execution phase, the code is interpreted and executed.
Each execution environment has a variable object (variable object) associated with it, and all variables and functions defined in the environment are saved in this object. You need to know that we can't manually access this object, only the parser can access it.
Scope Chain (the scope Chain)
When code executes in an environment, a scope chain (scope chain) is created for the variable object. The purpose of a scope chain is to ensure orderly access to all variables and functions that have access to the execution environment. The scope chain contains variable objects that correspond to each execution environment in the environment stack. Through the scope chain, you can determine the access of variables and the resolution of identifiers. Note that the variable object of the global execution environment is always the last object in the scope chain. Let's take a look at an 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 you cannot access Tempcolor
swapcolors () ;
}
ChangeColor ();
Only color
console.log ("Color is now" + color) can be accessed here;
The above code includes a total of three execution environments: Global environment, ChangeColor () Local environment, swapcolors () Local environment. The scope chain of the above program is shown in the following illustration:
Found from the above figure. 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.
For identifier resolution (a variable name or function name search) is the process of searching for identifiers at the first level along the scope chain. The search process always starts at the front end of the scope chain and then goes back and forth (the Global Execution Environment) backwards until the identifier is found.
Closed Bag
A closure is a function that has access to a variable in the scope of another function. In other words, when you define a nested function within a function, it forms a closure that allows nested functions to access variables of the outer function. By returning nested functions, you are allowed to maintain access to local variables, parameters, and internal function declarations in external functions. This encapsulation allows you to hide and protect the execution environment in an external scope, and exposes the public interface to perform further operations through the public interface. A simple example can be seen:
function foo () {
var localvariable = ' private variable ';
return function bar () {return
localvariable;
}
}
var getlocalvariable = foo ();
GetLocalVariable ()//private variable
One of the most popular closures in module mode, which allows you to simulate public, private, and privileged members:
var Module = (function () {
var privateproperty = ' foo ';
function Privatemethod (args) {
//do something
} return
{
publicproperty: ',
Publicmethod: function (args) {
//do Something
},
privilegedmethod:function (args) {return
privatemethod (args);
}
};
}) ();
A module is similar to a single instance object. Because we used (function () {...}) in the above code (); The anonymous function form, so it executes immediately when the compiler resolves it. The only object that can be accessed outside the execution context of the closure is the public methods and properties that are in the returned object. However, because the execution context is saved, all private properties and methods will always exist throughout the lifecycle of the application, which means that we can interact with them only through public methods.
Another type of closure is called a function expression (Iife) that executes immediately. It's simple, but it's just an anonymous function that executes in the global environment:
(function (window) {
var foo, bar;
function Private () {
//do something
}
window. Module = {
public:function () {
//do Something
}}
;
}) (this);
This expression is useful for protecting global namespaces from variable contamination by building function scopes to isolate variables from global namespaces and to allow them to exist throughout the runtime (runtime) in the form of closures. In many applications and frameworks, this way of encapsulating the source code is very popular, usually by exposing a single global interface to interact with the outside.
Call and apply
These two methods are built into all functions (they are prototype methods of function objects), allowing you to execute functions in a custom context. The difference is that the call function requires a list of arguments, and the Apply function requires you to provide an array of arguments. As follows:
var o = {};
function f (A, B) {return
a + b;
}
The method of function f as O is actually to reset the context of function F
f.call (O, 1, 2);//3
f.apply (o, [1, 2]);//3
Two results are the same, function f is invoked in the context of object o, and provides two identical parameters 1 and 2.
In ES5, the Function.prototype.bind method is introduced to control the execution context of a function, it returns a new function, and the new function is permanently bound to the object specified by the first parameter of the Bind method, regardless of how the function is used. It directs the function to the correct context by closing the package. For a lower version of the browser, we can simply implement it as follows (Polyfill):
if (! (' Bind ' in Function.prototype)} {
Function.prototype.bind = Function () {
var fn = This, context
= arguments[ 0],
args = Array.prototype.slice.call (arguments, 1);
return function () {return
fn.apply (context, args.concat (arguments));
}
}
The bind () method is often used in scenarios where context is missing, such as object-oriented and event handling. This is done because the AddEventListener method of the node always executes the callback function in the context of the node to which the event handler is bound, which is what it should behave like. However, if you want to use advanced object-oriented technology, or need your callback function to become an instance of a method, you will need to manually adjust the context. This is where the bind method comes in handy:
function MyClass () {
this.element = document.createelement (' div ');
This.element.addEventListener (' Click ', This.onClick.bind (this), false);
}
MyClass.prototype.onClick = function (e) {
//do something
};
Looking back at the source code for the Bind method above, you may notice that two calls involve the slice method of the array:
Array.prototype.slice.call (arguments, 1);
[].slice.call (arguments);
We know that the arguments object is not a real array, but a class array object that has the length property and the values can be indexed, but they do not support native array methods, such as slice and push. However, because they have arrays of similar behavior, the array's methods can be invoked and hijacked, so we can do this in a way similar to the code above, the core of which is to use the call method.
This technique of invoking other object methods can also be applied to object-oriented, and we can simulate classic inheritance in javascript:
MyClass.prototype.init = function () {
//Call the superclass Init method in the context of the "MyClass" instance
M YSuperClass.prototype.init.apply (this, arguments);
That is, a method of invoking a superclass (Mysuperclass) in an instance of a subclass (MyClass) by using call or apply.
The arrow function in ES6
The arrow function in ES6 can be used as a substitute for Function.prototype.bind (). Unlike a normal function, the arrow function does not have its own this value, and its this value inherits from the perimeter scope.
For a normal function, it always receives an this value automatically, and this point depends on how it is invoked. Let's take a look at an example:
var obj = {
///...
Addall:function (Pieces) {
var self = this;
_.each (pieces, function (piece) {
self.add (piece);
});
},
//...
}
In the above example, the most direct idea is to use This.add (piece) directly, but unfortunately you can't do that in JavaScript because each callback function does not inherit this value from the outer layer. In this callback function, the value of this is window or undefined, so we use the temporary variable self to import the external this value internally. There are two ways we can solve this problem:
Using the bind () method in ES5
var obj = {
///...
Addall:function (Pieces) {
_.each (pieces, function (piece) {
this.add (piece);
}. Bind (this));
},
//...
}
Using the arrow functions in ES6
var obj = {
///...
Addall:function (Pieces) {
_.each (pieces, piece => this.add (piece));
},
//...
}
In the ES6 version, the AddAll method obtains the this value from its caller, and the intrinsic function is an arrow function, so it integrates the this value of the outer scope.
Note: For the callback function, in the browser, this is window or undefined (strict mode) in the callback function, and in Node.js, this is global for the callback function. The instance code is as follows:
function Hello (A, callback) {
callback (a);
}
Hello (' Weiwei ', function (a) {
Console.log (this = = global);//True
Console.log (a);//Weiwei
});
Summary
Before you learn advanced design patterns, it is important to understand these concepts, because scopes and contexts play a fundamental role in modern JavaScript. Whether we're talking about closures, object-oriented, inheritance, or a variety of native implementations, contexts and scopes play a vital role. If your goal is to be proficient in the JavaScript language and to understand its composition in depth, the scope and context are your starting point.
The above content is small to introduce the JavaScript in the scope and context, I hope to help!