In-depth analysis of the scope and context of JavaScript, analysis of javascript
Scope and context in javascript are unique in this language, thanks in part to their flexibility. Each function has different variable context and scope. These concepts are the backing of some powerful Design Patterns in javascript. However, this also brings great confusion to developers. The following fully reveals the differences between context and scope in javascript and how to use them in various design patterns.
Context and Scope)
First, we need to know that context and scope are two completely different concepts. Over the years, I have discovered that many developers will confuse these two concepts (including myself) and mistakenly confuse them. In all fairness, many terms have been used in disorder over the years.
Each call to a function has a closely related scope and context. Basically, the scope is based on functions, while the context is based on objects. In other words, the scope involves variable access in the called function, and different call scenarios are different. Context is always the value of the this keyword. It is a reference to an object that owns (Controls) the current code to be executed.
Variable Scope
A variable can be defined in a local or global scope, which establishes different scopes for variable access during runtime. Any defined global variable means that it must be declared outside the function body and survive the entire runtime and can be accessed in any scope. Before ES6, local variables can only exist in the function body, and each function call has a different scope. Local variables can only be assigned, retrieved, and manipulated within the scope of the called period.
Before ES6, JavaScript does not support block-level scopes, which means block-level scopes cannot be supported in if statements, switch statements, for loops, and while loops. That is to say, JavaScript before ES6 cannot construct block-level scopes similar to those in Java (variables cannot be accessed outside the block of the statement ). However, from ES6, you can use the let keyword to define variables. It fixes the shortcomings of the var keyword, allows you to define variables like Java, and supports block-level scope. Let's look at two examples:
Before ES6, we used the var keyword to define the variable:
function func() {if (true) {var tmp = 123;}console.log(tmp); // 123}
The reason for access is that the variable declared by the var keyword has a process of variable escalation. In ES6, we recommend that you use the let keyword to define variables:
function func() {if (true) {let tmp = 123;}console.log(tmp); // ReferenceError: tmp is not defined}
This method can avoid many errors.
What is this context?
The context usually depends on how the function is called. When a function is called as a method in an object, this is set to the object that calls this method:
var obj = {foo: function(){alert(this === obj); }};obj.foo(); // true
This criterion is also applicable when the new operator is used to create an instance of an object when a function is called. In this case, the value of this in the function scope is set to the newly created instance:
function foo(){alert(this);}new foo() // foofoo() // window
When a binding function is called, this is the global context by default and points to the window object in the browser. Note that ES5 introduces strict mode. If strict mode is enabled, the context is undefined by default.
Execution context)
JavaScript is a single-threaded language, meaning that only one task can be executed at a time. When the JavaScript interpreter initializes the execution Code, it first enters the execution context by default. From now on, a new execution environment will be created for each function call.
This is often confusing for new users. Here we mention a new term, execution context, which defines other data that variables or functions have access, determines their respective actions. It is more biased towards the scope, rather than the Context we discussed earlier ). Be sure to carefully differentiate the two concepts of the execution environment and context (Note: English is prone to confusion ). To be honest, this is a very bad naming convention, but it is developed by the ECMAScript specification. You should follow it.
Each function has its own execution environment. When the execution stream enters a function, the function environment is pushed into an environment stack (execution stack ). After the function is executed, the stack pops up its environment and the control is returned 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 stages: creation and execution. In the creation phase, the parser first creates a variable object (variable object, also known as the activation object), which consists of variables, function declarations, and parameters defined in the execution environment. At this stage, the scope chain will be initialized, and the value of this will be finalized. In the execution phase, the Code is interpreted and executed.
Each execution environment has a variable object associated with it. All variables and functions defined in the environment are stored in this object. You need to know that we cannot manually access this object, and only the parser can access it.
The Scope Chain)
When the code is executed in an environment, a scope chain of the variable object is created ). The purpose of the scope chain is to ensure orderly access to all variables and functions that the execution environment has the right to access. The scope chain contains variable objects corresponding to each execution environment in the Environment stack. Through the scope chain, you can determine the access to variables and the resolution of identifiers. Note: The variable objects in the global execution environment are always the last objects in the scope chain. Let's 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 tempColorswapColors () ;}changecolor (); // you can only access colorconsole. log ("Color is now" + color );
The preceding Code consists of three execution environments: Global Environment, changeColor () local environment, and swapColors () local environment. Shows the scope chain of the above program:
From Discovery. 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 ordered.
Identifier resolution (variable name or function name search) is the process of searching identifiers at the first level along the scope chain. The search process always starts from the front end of the scope chain, and then traces back to (Global execution environment) Step by step until the identifier is found.
Closure
A closure is a function that has the right to access variables in another function scope. In other words, when a nested function is defined in a function, a closure is formed, which allows the nested function to access the variable of the outer function. By returning nested functions, you can 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, expose public interfaces, and then perform further operations through public interfaces. Let's look at a simple example:
function foo(){var localVariable = 'private variable';return function bar(){return localVariable;}}var getLocalVariable = foo();getLocalVariable() // private variable
One of the most popular closure types 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);}};})();
The module is similar to a singleton object. Since the above Code uses the anonymous function form of (function () {...}) ();, It will be executed immediately when the compiler parses it. The only externally accessible object in the execution context of the closure is the public methods and attributes in the returned object. However, because the execution context is saved, all private attributes and methods will always exist throughout the life cycle of the application, which means that we can only interact with them through public methods.
Another type of closure is called the function expression for immediate execution (IIFE ). In fact, it is very simple, just an anonymous function that is self-executed in the global environment:
(function(window){ var foo, bar;function private(){// do something}window.Module = {public: function(){// do something }};})(this);
This expression is very useful for protecting global namespaces from variable pollution. It isolates variables from global namespaces by constructing function scopes, and let them exist in the entire runtime through the form of closures ). In many applications and frameworks, this method of encapsulating source code is very useful. It is usually used to expose a single global interface to interact with the outside.
Call and Apply
These two methods are built in all functions (they are the prototype methods of the Function object), allowing you to execute functions in a custom context. The difference is that the call function requires a list of parameters, and the apply function requires you to provide an array of parameters. As follows:
Var o ={}; function f (a, B) {return a + B;} // The method that uses function f as o is actually to reset the context f of function f. call (o, 1, 2); // 3f. apply (o, [1, 2]); // 3
The two results are the same. function f is called in the context of object o, and two identical parameters 1 and 2 are provided.
Function is introduced in ES5. prototype. the bind method is used to control the execution context of a function. It returns a new function and is permanently bound to the object specified by the first parameter of the bind method, no matter how the function is used. It directs the function to the correct context through the closure. For earlier browsers, 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 usually used in scenarios where context is lost, such as object-oriented and event processing. This is because the addEventListener method of the node always executes the callback function in the context of the node bound to the event processor. This is what it should do. However, if you want to use advanced object-oriented technology or require your callback function to become an instance of a method, you will need to adjust the context manually. This is the convenience of the bind method:
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 of the above bind method, you may notice that two calls involve the Array slice method:
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. Although it has the length attribute and its values can be indexed, they do not support native array methods, for example, slice and push. However, because they have similar behavior as arrays, the array methods can be called and hijacked, so we can achieve this through code similar to the above, the core is to use the call method.
This method of calling other object methods can also be applied to object-oriented systems. We can simulate the classic Inheritance Method in JavaScript:
MyClass.prototype.init = function(){// call the superclass init method in the context of the "MyClass" instanceMySuperClass.prototype.init.apply(this, arguments);}
That is, call or apply is used to call the superclass method in a subclass (MyClass) instance.
Arrow functions in ES6
The arrow Function in ES6 can be used as a substitute for Function. prototype. bind. Unlike a common function, an arrow function does not have its own this value. Its this value inherits from the peripheral scope.
For a common function, it always automatically receives a value of this, and the point of this depends on the method it calls. Let's 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 directly use this. add (piece), but unfortunately you cannot do this in JavaScript, because the callback function of each does not inherit this value from the outer layer. In this callback function, the value of this is window or undefined. Therefore, we use the temporary variable self to import the external value of this into the internal. There are two ways to solve this problem:
Use the bind () method in ES5
var obj = {// ...addAll: function (pieces) {_.each(pieces, function (piece) {this.add(piece);}.bind(this));},// ...}
Use the arrow function in ES6
var obj = {// ...addAll: function (pieces) {_.each(pieces, piece => this.add(piece));},// ...}
In ES6, The addAll method obtains the this value from its caller. The internal function is an arrow function, so it integrates the this value of the external scope.
Note: For callback functions, in the browser, this in the callback function is window or undefined (strict mode), while in Node. js, this in the callback function is global. The instance code is as follows:
function hello(a, callback) {callback(a);}hello('weiwei', function(a) {console.log(this === global); // trueconsole.log(a); // weiwei});
Summary
Understanding these concepts is important before you learn advanced design patterns, because scopes and contexts play the most basic role in modern JavaScript. Whether we are talking about closures, object-oriented, inheritance, or various native implementations, context and scope play a crucial role in them. If your goal is to be proficient in the JavaScript language and have a deep understanding of its components, the scope and context are your starting point.
The above content is the scope and context of JavaScript, which I hope to help you!
Articles you may be interested in:
- Analysis of JavaScript scope chain, execution context and Closure
- In-depth understanding of the scope and context in Javascript
- Overview of the scope and context in javascript