How does JavaScript work as an interpreted language

  • Engine: Responsible for compiling and executing the entire JavaScript code
  • Compiler: Performs word segmentation, parsing, and generating engine execution code (although JavaScript is an interpreted language, it also has a compilation process)
  • Scope: Manages and maintains all declarations and variables, and determines which variables the currently executing code has access to

A scope is like a helper, collecting and managing variables at compile time, and deciding which variables can be accessed at engine execution

Lexical scope

There are two types of scope, lexical scope and dynamic scope. Most programming languages use the first, including JavaScript. The lexical scope depends on where the variable and block scopes are written and is determined as the compiler parses the lexical scope. The purpose of this scope is to assist the engine in compiling and executing code.

My understanding is that the lexical scope is determined by the curly braces, so it is determined by the scope in which the code is written.

Dynamic lexical scoping is certainly possible in JavaScript through with and eval, but it is not recommended because dynamic scoping does not allow the engine to be better optimized for execution and therefore affects performance.

Function scope and closure

Is the most common scope in JavaScript. A function’s internal information is hidden from the outside, but it can access the outside information, so nested functions also form scope chains. You can use self-executing functions and closures to take advantage of this feature.

Block scope

Unlike function scopes, block scopes are relatively rare in JS. Previously, block scopes were only formed in with and catch. Internal declarations cannot be referenced externally. Later came let and const, which declared variables that could be bound to the scope of the current bracketed block, but not to the block-generating scope.

5. Claim promotion

Because the scope is lexicographically defined during compiler execution, the declaration and assignment of a variable are pushed to the top of the scope, while function declaration is even more powerful, and both declaration and assignment are promoted.

The reason for this is that JS is a flexible scripting language, which intelligently reduces errors. However, as JS programs expand, this “clever” feature can introduce some unexpected bugs, as well as the repeated use of var to declare the same variable. This is because the compiler first looks to see if there are already declarations in the scope, and if there are, it overwrites them. To address these side effects, ES6 introduced let and const variable declarations, discarding these features.

A. this B. this C. this D. this

When a function is called, an execution context is created to record all the information, including the call stack, how it was called, and this is one of the properties that points to the calling object. This also means that this is determined at function call time.

This is also why beginners can be confused about the orientation of this, because unlike scope chains, which are identified at the lexical stage, this is dynamic, so it can be confusing. Instead of binding the calling object, the arrow function finds the this of the previous layer through the scope chain.

The four operations that determine what this refers to, in order of priority:

  1. New Binding object;
  2. Call, apply, bind explicitly;
  3. Object properties directly called;
  4. Normal function calls that implicitly bind global objects, or undefined in strict mode.

Seven, objects,

Before, I always had two puzzles. One is why JS has basic types such as string and number, which are different from Object, and why they also have properties and methods. Second, why do we have String, Number and other constructors to construct corresponding objects, but we still need so-called basic types?

The reason for the first problem is that when declaring a string like var s = ‘ABC ‘; When we select s.length, the engine automatically converts the base type string to string, so we have the length property. As for the second problem, the optimization of memory should be considered. After all, the base type only exists on the stack, unlike the object which needs a construction process, and has the advantage of performance.

Objects have attribute descriptors, which, as the name suggests, describe or specify the attributes of an object. DefineProperty is configured with value, Writable, Enumerable, and any additional information that can be added, deleted, modified, and controlled by these four attributes.

[[GET]] is a built-in method that every object has to access a property. It is equivalent to GET() every time it accesses a property. One detail is that this method tries to look up the property on the prototype chain if the property is not available to the object. There is also a built-in [[PUT]] method for setting properties.

The above two configuration methods can also be replaced by getters and setters in properties, which are also modified by the developer via Object.defineProperty. In fact, getters and setters are called access descriptors. By setting the getters and setters of object properties, you can also control the effect of adding, deleting, changing, and checking properties, but with more flexibility.

Prototype chain

Every object has a [[prototype]] built-in property that points to the object’s prototype, the shape of the prototype chain. So why a prototype? This is something I’ve always wondered about, because while it sounds convenient to go up and find properties, it introduces an incomprehensible complexity to the language.

As if the programming can create the world, we in order to avoid repeated description, generally describes some general “encapsulation” rise, such as traffic tools usually have the engine, it is the source of power, so we when creating cars, ships, aircraft, don’t need to repeat to describe engine in this matter, because it must have the engine is a transportation tool. So I think the purpose of stereotypes is to “encapsulate” certain descriptions, sort of like the concept of classes and class inheritance. But class inheritance is actually a way of copying, because parent and child classes are unrelated, whereas prototype chains are not.

And since everything in JavaScript is an object, in order to ensure performance, copying objects is actually just copying reference addresses, so it is natural to come into being the way of prototype chain, to achieve the purpose of “inheritance” by associating objects. Prototype chain is the product of this idea.

When you try to retrieve an object’s properties, you call its internal method [[GET]], which looks for the object and then up the prototype chain.

In the same way, when you set myObj. Foo =123, it looks for myObj itself and overwrites it if it does. If myObj doesn’t have the same property, I thought it would add it directly, but it doesn’t, it still looks for the prototype chain, and if the upper level object has the same property, it just blocks it out, and then adds it to myObj, which sounds pretty much the same as adding it directly, The problem is that if a property of the same name set on the prototype chain is not writable, the operation will not proceed, but will either report an error (in strict mode) or simply fail silently. If a setter is set on the prototype chain, the setter will be called directly without adding foo to myObj. Using defineProperty can avoid this minor problem.

Nine, Promise

Prior to ES6, asynchronous operations were dependent on the JavaScript host environment, so asynchronous operations were usually performed in a thread and then told js to execute the callback. However, ES6 introduces event loop mechanism and brings asynchronous management into the scope of JS engine.

So why do we need to introduce time loops or why do we need promises? This is mainly to solve the problem of traditional JS asynchronous callbacks: callback hell and the uncertainty of callbacks. Promise’s then and catch clearly describe asynchronous steps that resolve callback hell. The promise. Then call only once also avoids the instability of asynchronous callbacks (callers of callbacks can be uncontrolled, called multiple times, or swallow errors).

A promise is a promise that you’ll get a result and THEN I’ll do the next thing, whereas a callback is telling the person what I’m going to do until he’s willing to do it for me. I’m not sure how many times he’ll do it or if he’ll make a mistake. In short, using callbacks to handle asynchronous events is a tricky business.

There are also points, such as promise.all, which addresses multiple asynchronous shared results, and race, which addresses races, where only synchronous operations must also be enqueued, which addresses synchronous code leading to incorrect execution order expectations. These are the icing on the cake grammatical sugar.

One thing to note about promise error handling:

Resolve promises the passed argument. If the passed argument has a THEN attribute, it is promised and the call is expanded
p = Promise.resolve(123) 
p.then((res) = > {
    throw Error(a)/ / an error
    console.log(res) / /!!!!! This is never executed
}, (err) = > {
    // I thought it would work here, but it doesn't. Because P.Teng will actually return a default promise.resolve (undefined)
    P and P.chen are separate promises, so the reject callback will not be called
    console.log('error 1') 
}).then(() = > {
    console.log("Will it be implemented here?") // Does not execute
    Reject (err) => promise.reject (err)
    // So the error will continue to be passed
}).catch((err) = > {
    // this is where the p.chen error is caught
    console.log('error 2')})// Result: error 2
Copy the code

Tenth, the event cycle

JavaScript maintains an execution stack, pushing all the context of a function into a stack frame, and then pushing it out of the stack, while asynchronous operation events maintain a queue that loops through the callback push from the queue.

The queue is cleared when the stack is empty, and the rule is that in each loop, the following three steps are continuously performed on the queue:

  • Perform one macro task (all tasks running on external event sources, in this case browser DOM manipulation, Ajax, user interaction, History Api, timer, script tag execution)
  • Perform all microtasks in the queue (Promise and MutationObserver added after ES6)
  • To render the page
setTimeout(() = > {
  console.log('setTimeout 1');
  
  Promise.resolve().then(() = > {
    console.log('Promise 3');
  }).then(() = > {
    console.log('Promise 4');
  });
});

Promise.resolve().then(() = > {
  console.log('Promise 1');
}).then(() = > {
  console.log('Promise 2');
});
console.log('start')

setTimeout(() = > {
  console.log('setTimeout 2');
});

/** Result: "start" "Promise 1" "Promise 2" "setTimeout 1" "Promise 3" "Promise 4" "setTimeout 2" */
Copy the code

One thing to note here is that I should have executed a macro task (setTimeout) at least once in my loop. Why did I execute all the Promise results first