Meaning of scope
Scope refers to the scope that a function or variable can access.
There are three important steps in code compilation (in the compiler) :
- Lexical analysis: Breaking a string of characters into meaningful fragments, called tokens.
- Grammar analysis: will a
token
Is replaced by a Tree of nested elements (Abstract Syntax Tree, AST, Abstract Syntax Tree). - Code generation: Convert abstract syntax trees into executable code
There are two important lookups in word segmentation: LHS and RHS
- LHS: means “to bestow… Value “, or “target of assignment”, for example
a=3
. An assignment that is not fulfilled may throw oneTypeError
Indicates that an illegal/impossible action was attempted on the result. - RHS: means “to get… “, or means “source of assignment”, for example
console.log(a)
. Unmet RHS can causeReferenceError
An error is thrown.
Lexical scope
There is no dynamic scope in JS, only lexical scope. Lexical scope is determined based on where the variable and the scoped block are at the time the code is written. When a match is made, only the first variable found is matched.
There are ways to cheat lexical scope in JS, but cheating lexical scope can lead to performance degradation. This is because the JS engine cannot statically analyze these grammars to optimize execution efficiency. Don’t use them unless you absolutely have to.
eval
The eval() function takes a string as a parameter value and runs it as code on the fly.
function foo(str, a) {
eval(str);
console.log(a, b);
}
var b = 2;
foo('var b=3'.1); / / 1 3
Copy the code
with
Note: With has a side effect of variable leakage: At run time, with converts an object and its properties into a “scope” with an “identifier” that is leaked globally when assigned to a variable that does not exist in the object. But look at how it’s written:
var obj = {
a: 1.b: 2.c: 3
};
// repeat "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// The abbreviation for simpler
with (obj) {
a = 3;
b = 4;
c = 5;
}
Copy the code
Function scope, scope chain, block scope
Historically, JavaScipt has only global scope and function scope. It wasn’t until ES6 that block-level scopes were added.
Let’s look at each of these different scopes:
1. Function scope
Function scope meaning:
- Follow the least privilege
- To avoid conflict
- Creating a global “namespace”
- Module management
Anonymity and naming
setTimeout( function(){
console.log("I waited 1 second!");
}, 1000 );
Copy the code
Anonymous function expressions can be typed quickly, but have the following disadvantages:
- Anonymous functions without names on the stack trace can make debugging more difficult
- Without a name, when the function needs to be used recursively, only through
arguments.callee
The deprecated API - Anonymous function expressions do not help code readability.
The best practice is to always name your function expressions.
IIFE
Function scope has a common Immediately Invoked Function Expression, IIFE (Immediately Invoked Function Expression).
There are two common ways to write IIFE, depending on which pair () is executed:
Writing a:
(function foo(){
var a = 10;
console.log(a); }) ();Copy the code
Method 2:
(function foo(){
var a = 10;
console.log(a); } ());Copy the code
The effect is the same, depending on your style, the first is more common.
There are several applications of IIFE:
// Usage 1: Passing in the window is used to isolate some global variables, or to smooth out differences between global objects, etc
(function IIFE(global) {
var a = 3;
console.log(a); / / 3
console.log(global.a); / / 2}) (window);
// Usage 2: ensure that undefined in a code block is indeed undefined
undefined = true; // Mine other code! Don't do that!
(function IIFE(undefined) {
var a;
if (a === undefined) {
console.log('Undefined is safe here! ');
}
})();
// Usage 3: Reverse IIFE for UMD (Universal Module Definition -- Universal Module Definition)
var a = 2;
(function IIFE(def) {
def(window); }) (function def(global) {
var a = 3;
console.log(a); / / 3
console.log(global.a); / / 2
});
Copy the code
Function declarations and function expressions
Here is the function declaration:
// Function declaration
function funDeclaration(type){
return type==="Declaration";
}
Copy the code
Here is the function expression:
var funExpression = function(type){
return type==="Expression";
}
Copy the code
- Function expressions differ from function declarations in that function names are only valid inside the function, and the binding is a constant binding.
- Assignment to a constant is an error in strict mode, and silent failure in non-strict mode.
Take a look at the following code:
var b = 10;
(function b() {
b = 20; // b is a function declaration. Constant bindings cannot be assigned
console.log(b); }) ();// output: function b(){... }
Copy the code
But if you reassign inside the function:
(function b() {
let b = 20; // b is redeclared here, overwriting the function declaration
console.log(b); }) ();// Output: 20
Copy the code
- Functions in IIFE are function expressions, not function declarations.
Note: Functions created with function declarations can be called before the function definition, but functions created with function expressions cannot. This is because of the different modes of ascension.
2. Scope chain
The whole execution process of JavaScript code is divided into two stages, code compilation stage and code execution stage. The compilation phase is done by the compiler, which translates the code into executable code, where scope is defined, and the execution phase is done by the engine, which creates execution context.
During context generation, the value of the variable object, scope chain, and this are determined separately.
A scope chain, consisting of objects from the current environment and the upper environment, ensures that the current execution environment has orderly access to variables and functions that qualify for access rights.
We use an example to explain the scope chain:
var a = 20;
function test() {
var b = a + 10;
function innerTest() {
var c = 10;
return b + c;
}
return innerTest();
}
test();
Copy the code
In this example, the execution context for global, test, and innerTest is created in sequence, and the variable objects are VO(global),VO(test), and VO(innerTest), respectively. The innerTest’s scope chain contains all three variable objects:
innerTestEC = {
VO: {},
[[SCOPE]]: [VO(innerTest), VO(test), VO(global)]};Copy the code
The [[SCOPE]] here is the SCOPE chain. In general, the first item is the current scope and the last item is the global variable object.
The current scope and the upper scope are actually a chain relationship, rather than a containment relationship. It is a one-way linked list that allows us to access variables in the upper scope.
3. Block scope
Before ES6, JS didn’t really have a simple block scope. But we can try… Catch to create block scope:
try {
undefined(a);// Force an exception with an illegal operation!
} catch (err) {
console.log(err); / / to use!
}
console.log(err); // ReferenceError: `err` not found
Copy the code
Since ES6, let, const were introduced to create block scope. Its main features are:
- Block scope is created
- Forbid claiming promotion and create TDZ(temporal Dead zone)
- Duplicate declarations are not allowed
closure
Closures are the core concept of JS, simply defined as: a function can remember and access its lexical scope, even when the function is executed outside its lexical scope.
Closures have some very important applications, and here are just a few:
1. Simply create a module
function CoolModule() {
var something = 'cool';
var another = [1.2.3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join('! ')); }}var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); / / 1! 2! 3
Copy the code
2. Use closures in loops
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
} //'6' is printed 5 times, once per second
Copy the code
3. Use closures in Currization
// Simple implementation, parameters can only be passed from right to left
function createCurry(func, args) {
var arity = func.length;
var args = args || [];
return function() {
var _args = [].slice.call(arguments);
[].push.apply(_args, args);
// If the number of arguments is less than the original func.length, the recursive call continues to collect arguments
if (_args.length < arity) {
return createCurry.call(this, func, _args);
}
// Func is executed when the parameters are collected
return func.apply(this, _args);
};
}
Copy the code
Usage:
function check(targetString, reg) {
return reg.test(targetString);
}
var _check = createCurry(check);
var checkPhone = _check(/^1[34578]\d{9}$/);
var checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
Copy the code
Dynamic scope
Since there is no real dynamic scope in JS, the concept of dynamic scope, we sort out two points:
- Dynamic scopes are determined at runtime, not statically at authoring time. Dynamic scope does not care where and how functions’ scopes are declared, but rather where they are called from; in other words, its scope is based on the call stack, not the nesting of scopes in the code.
- JS has no actual dynamic scope, but the binding mechanism for this is somewhat similar to dynamic scope, since this is concerned with how functions are called.
this
This part introduces the grammar and knowledge points related to this.
First, this does not refer to the function itself, nor does it refer to the lexical scope of the function in any way. This is bound at run time, depending on the context of the function call. The this binding has nothing to do with where the function is declared and everything to do with how the function is called.
From the above introduction, we know that when a function is called, an execution environment is set up, which contains the call stack of the function, the way it is called, and the parameters to be passed. One of the attributes of this record is the this pointer during the execution of the function.
This is a complete basisCall the point
The binding created for each function call
A call-site is the location of the statement that executes the function.
There are several common binding mechanisms for this:
1. Default Binding
Independent function call
function foo() {
console.log(this.a);
}
var a = 2;
foo(); / / 2
Copy the code
If in strict mode, a does not point to a global object, a undefined exception is raised: TypeError: This is undefined.
Here’s a subtle detail: even though all of the this binding rules are strictly call point based, if the contents of foo() are not executed in strict mode, the global object is uniquely legal for the default binding; The strict mode state of foo() ‘s call point is irrelevant here:
function foo() {
console.log( this.a );
}
var a = 2;
(function(){
"use strict";
foo(); / / 2}) ();Copy the code
Of course, this local mix of strict patterns is a very bad practice. Your code as a whole should be in strict or non-strict mode.
2. Implicit Binding
In this way, we consider whether the call has a context object, also known as an owner or a container, as in the following code:
function foo() {
console.log(this.a);
}
var obj = {
a: 2.foo: foo
};
obj.foo(); / / 2
Copy the code
Implicitly Lost: When an implicit binding loses the binding, it usually falls back to the default binding, with either global or undefined depending on strict mode.
function foo() {
console.log(this.a);
}
var obj = {
a: 2.foo: foo
};
var bar = obj.foo; // Function reference!
var a = 'oops, global'; // 'a' is also a property of a global object
bar(); // "oops, global"
Copy the code
The function foo does not belong to the object obj, which simply has a reference to foo, so bar has nothing to do with obj.
3. Explicit Binding
If you want to force a function call to use a particular object as this binding, instead of preventing a function from referencing properties on that object. You can use explicit binding. Specifically, functions have call and apply methods. These two arguments take the first argument to the object of this, and then only the specified this is used to call the function. Because you’ve already indicated directly what you want this to be. This type of binding is called explicit binding.
The difference between the two arguments is that call accepts a list of arguments except for the first one, while Apply accepts only an array of arguments.
let a = {
value: 1
};
function getValue(name, age) {
console.log(name);
console.log(age);
console.log(this.value);
}
getValue.call(a, 'cxk'.'24');
getValue.apply(a, ['cxk'.'24']);
Copy the code
Call’s performance is better than Apply’s, especially since ES6 introduced the Spread operator(extended operator), which allows you to use Call even if the arguments are arrays.
let params = [1.2.3.4]; xx.call(obj, ... params);Copy the code
The problem with explicit binding, however, is that it still doesn’t solve the problem: functions lose their original This binding or are overwritten by third-party frameworks.
Writing acall/apply
call:
Function.prototype.myCall = (content, ... args){let context = content || window;
context.fn = this
letres = context.fn(... args)delete context.fn;
return res
}
Copy the code
The apply similar:
Function.prototype.myApply = (content, args) {
let context = content || window;
context.fn = this
letres = context.fn(... args)delete context.fn;
return res
}
Copy the code
4. Hard Binding
However, there is a display binding variant that can implement this technique. Consider this code:
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var bar = function() {
foo.call( obj );
};
bar(); / / 2
setTimeout( bar, 100 ); / / 2
// 'bar' hardbinds foo's 'this' to' obj '
// So it cannot be overwritten
bar.call( window ); / / 2
Copy the code
In this code, we create a function bar() that manually calls foo.cal() inside it, thereby forcing this to bind to obj and call foo. No matter how bar is later called, it always calls foo manually using obj. This binding is firm and unambiguous, so it is called hard binding
The most typical way to wrap a function with hard binding is to create a channel for all incoming and outgoing parameter return values.
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code
Another way to express this pattern is to create a reusable help function:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
// A simple 'bind' helper function
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
var obj = {
a: 2
};
var bar = bind( foo, obj );
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code
This approach is so common that it is built into ES5 as a built-in function.
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = foo.bind( obj );
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code
bind(…) Returns a hard-coded new function that calls the original function using the this environment you specified.
Note that in ES6, the hard-binding function generated by bind has a property named.name, derived from the original target function.
bind
Implementation principle of
Function.prototype.myBind = function(context) {
// Can only accept function as this
if (typeof this! = ='function') {
throw new TypeError('Error');
}
var _this = this; // Target function
// Get all the arguments passed
var args = [...arguments].slice(1);
// Return a function
return function F() {
// Since it returns a function, we can use new F().
// The original function should be called new, not our F,
// So return new _this
if (this instanceof F) {
return new_this(... args, ... arguments); }return_this.apply(context, args.concat(... arguments)); }; };Copy the code
5. New Binding (new Binding)
First, new in JS is not the same as new in traditional languages. New has the following actions:
- Create a new object
- Plug this object into the prototype chain
- Set this object to the this binding of the function call
- Unless the function specifies an object to return, the newly built object is returned by default
function _new(fn, ... arg) {
if(typeoffn ! = ='function') throw `${fn} is not a constructor`
// Create an object with fn. Prototype
const obj = Object.create(fn.prototype);
// Execute fn on this object to get the result
const ret = fn.apply(obj, arg);
// If the result is an object, return the object, otherwise the function is not returning an object, return obj
return ret instanceof Object ? ret : obj;
}
Copy the code
The priority of the four this binding rules is: new=> Call /apply=> Implicit binding => default binding
Additionally, if null or undefined is passed as this binding parameter in Call /apply, these values are ignored and the default binding is performed.
The implementation principle of new
When new is called, it actually does three things:
- Give instance objects access to private properties
- Give instance objects access to properties on the stereotype chain on which the constructor stereotype resides
- Consider the case where the constructor has a return value
function _new(fn, ... arg) {
if(typeoffn ! = ='function') throw `${fn} is not a constructor`
// Create an object with fn. Prototype
const obj = Object.create(fn.prototype);
// Execute fn on this object to get the result
const ret = fn.apply(obj, arg);
// If the result is an object, return the object, otherwise the function is not returning an object, return obj
return ret instanceof Object ? ret : obj;
}
Copy the code
6. Two special bindings
There are two special cases of binding, one of which is indirect reference:
function foo() {
console.log(this.a);
}
var a = 2;
var o = { a: 3.foo: foo };
var p = { a: 4 };
o.foo(); / / 3
(p.foo = o.foo)(); // 2, the scope of this is global
Copy the code
The second is soft binding:
// This is a soft binding tool
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this,
curried = [].slice.call(arguments.1),
bound = function bound() {
return fn.apply(
!this| | -typeof window! = ='undefined' && this= = =window) | | (typeof global! = ='undefined' && this= = =global)? obj :this,
curried.concat.apply(curried, arguments)); }; bound.prototype =Object.create(fn.prototype);
return bound;
};
}
Copy the code
Soft binding is a degenerate binding.
There are a few other things about the this binding
7. Arrow function
The arrow function is a new syntax in ES6. It has a special this binding that has several features:
- In vivo function
this
Object is the object at which you define it, not the object at which you use it. - Cannot be used as a constructor and cannot use the new command. because
- No one of their own
This, cannot be called
The call, the apply. - There is no
prototype
Properties, andnew
The command is executed with the constructorprototype
Value assigned to the new object__proto__
- No one of their own
- You can’t use
arguments
Object. - You can’t use
yield
Object.
summary
- When you call it as a function,
this
Point to thewindow
- When called as a method,
this
The object that points to the calling method - When called as a constructor,
this
Points to the newly created object - In order to
call
andapply
When I call it,this
Points to the specified object arguments
Refers to the current function when called
Execution context
Execution context is the abstract concept of the environment in which the current JS code is parsed and executed. Any code that runs in JS runs in an execution context.
The type of execution context
There are three types of execution contexts:
1. Global execution context
The default, underlying execution context. Code that is not in any function is in the global execution context. It does two things:
- Create a global object, which in the browser is the Window object
- Point the this pointer to the global object. Only one global execution context can exist in a program
2. Function execution context:
Each time a function is called, a new execution context is created for the function. Each function has its own execution context, but it is created only when the function is called.
Any number of function execution contexts can exist in a program. Each time a new execution context is created, it performs a series of steps in a specific order.
An Activation Object (AO) is a special Object that is created when a function is created.
In the execution context of a function, an active object is treated as an activity of a variable object (which is used as a variable object in the local execution context) and contains parameters and arguments objects.
In fact, variable objects serve the same purpose as active objects, to keep records of our variables.
3. Eval function execution context
The code running in the eval function has its own execution context. There will be performance loss and safety issues.
The lifecycle of the execution context
The implementation environment (EC) setup is roughly as follows:
Create phase interpreter, scan arguments passed to function, local function declarations and local variable declarations, and create EC objects.
The life cycle of an execution context consists of three phases: the create phase, the execute phase, and the reclaim phase.
1. Creation phase
When a function is called, but before any of its internal code is executed, it does several things:
- Create Variable Object (VO, Variable Object): Initializes the parameters of the function for the first time
arguments
, and promote function declarations and variable declarations.- Here variable declaration promotion is used
var
This happens when you create a variable if you uselet/const
There is no declared promotion mechanism, but there is also a pre-parsing process that puts declared variables into the variable object, except that andvar
Declared variables are stored in different locations.
- Here variable declaration promotion is used
- Scope Chain: The Scope Chain is created after the variable object during the creation phase of the execution-time context. The scope chain itself contains variable objects. Scope chains are used to resolve variables. When asked to parse a variable, JS always starts at the innermost layer of code nesting. If the innermost layer does not find the variable, it jumps to the parent scope of the next layer until the variable is found.
- Determine the value of the context in which this is executed
2. Implementation phase
Responsible for performing variable assignments, code execution
3. Recycling phase
The execution context goes out of the stack and waits for the VM to reclaim the execution context
This points to an explanation
As mentioned above, the value of this is confirmed during execution, not at definition (except for arrow functions). Because this is part of the execution context, the execution context needs to be determined just before the code is executed, not when it is defined.
Execution Context Stack
There is no limit to the number of execution contexts in a function. Each time a function is called, a new execution context is created.
The JavaScript engine creates an execution context stack to manage the execution context. You can think of the execution context stack as a stack structure for storing function calls.
From the flow chart, we can see:
- Js executes on a single thread, with all code executed in order
- To start the browser executing global code, it first creates the global execution context, pushing it to the top of the execution stack
- Each time a function is entered, the execution context of the function is created and pushed to the top of the execution stack. After the execution of the current function completes, the execution context of the current function goes off the stack and waits for garbage collection.
- The browser’s JS execution engine always accesses the execution context at the top of the stack
- There is only one global context, which is pushed out of the stack when the page is closed.
JS stack in memory
The heap and stack are both an area of data allocated in runtime memory and are therefore also referred to as the heap and stack area.
The difference between the two mainly lies in the data type and processing speed:
- Heap: Used to allocate space for complex data types (reference types), such as array objects, objects, which are dynamically allocated at runtime and thus slow to access.
- Stack: Mainly holds references to basic types of variables and objects, including pools (pools hold constants), which have the advantage of being faster to access than a heap, and the safeguards in the stack are shared. The disadvantage is that the size and lifetime of the data in the stack must be determined, which lacks flexibility.
Variables in ::: TIP closures are not stored on the stack, but in heap memory. This is why variables in closures can be accessed:
The contents of the JS heap do not need to be displayed by program code, because the heap is handled by the automatic garbage collection (GC) mechanism. The JS interpretation engine in each browser has a different garbage collection approach.
Refer to the link
- JS you didn’t know: Scopes and closures
- Front end basics (4) : detailed diagrams of scope chains and closures
- ECMAScript introduction to 6
- The front end foundation advanced (eight) : the detailed solution of the currization of the function
- What is the difference between call and apply
- In-depth understanding of JavaScript execution context and execution stack
- JavaScript execution context, execution stack, scope chain