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) :

  1. Lexical analysis: Breaking a string of characters into meaningful fragments, called tokens.
  2. Grammar analysis: will atokenIs replaced by a Tree of nested elements (Abstract Syntax Tree, AST, Abstract Syntax Tree).
  3. 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 examplea=3. An assignment that is not fulfilled may throw oneTypeErrorIndicates that an illegal/impossible action was attempted on the result.
  • RHS: means “to get… “, or means “source of assignment”, for exampleconsole.log(a). Unmet RHS can causeReferenceErrorAn 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:

  1. Follow the least privilege
  2. To avoid conflict
  3. Creating a global “namespace”
  4. 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 througharguments.calleeThe 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
  1. Function expressions differ from function declarations in that function names are only valid inside the function, and the binding is a constant binding.
  2. 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
  1. 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:

  1. Block scope is created
  2. Forbid claiming promotion and create TDZ(temporal Dead zone)
  3. 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:

  1. 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.
  2. 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 pointThe 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.

bindImplementation 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:

  1. Give instance objects access to private properties
  2. Give instance objects access to properties on the stereotype chain on which the constructor stereotype resides
  3. 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:

  1. In vivo functionthisObject is the object at which you define it, not the object at which you use it.
  2. Cannot be used as a constructor and cannot use the new command. because
    1. No one of their ownThis, cannot be calledThe call, the apply.
    2. There is noprototypeProperties, andnewThe command is executed with the constructorprototypeValue assigned to the new object__proto__
  3. You can’t useargumentsObject.
  4. You can’t useyieldObject.

summary

  1. When you call it as a function,thisPoint to thewindow
  2. When called as a method,thisThe object that points to the calling method
  3. When called as a constructor,thisPoints to the newly created object
  4. In order tocallandapplyWhen I call it,thisPoints to the specified object
  5. argumentsRefers 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:

  1. Create a global object, which in the browser is the Window object
  2. 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 timearguments, and promote function declarations and variable declarations.
    • Here variable declaration promotion is usedvarThis happens when you create a variable if you uselet/constThere is no declared promotion mechanism, but there is also a pre-parsing process that puts declared variables into the variable object, except that andvarDeclared variables are stored in different locations.
  • 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:

  1. Js executes on a single thread, with all code executed in order
  2. To start the browser executing global code, it first creates the global execution context, pushing it to the top of the execution stack
  3. 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.
  4. The browser’s JS execution engine always accesses the execution context at the top of the stack
  5. 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

  1. JS you didn’t know: Scopes and closures
  2. Front end basics (4) : detailed diagrams of scope chains and closures
  3. ECMAScript introduction to 6
  4. The front end foundation advanced (eight) : the detailed solution of the currization of the function
  5. What is the difference between call and apply
  6. In-depth understanding of JavaScript execution context and execution stack
  7. JavaScript execution context, execution stack, scope chain