directory

2. Execute context and Scope - Execute context - Scope - Modify scope - Temporary dead zone 3. Prototypes and Inheritance - Prototypes and prototype chains - Inheritance - Performance in JSCopy the code

preface

This article is an original article, ten thousand words long, it is recommended to collect after reading. Why so many JS articles, more and more dizzy? More and more vague? Try this one. This is an in-depth study, not an introductory article. If it helps you, please bookmark, like, comment, reprint please indicate the source.

What is not specified in this article refers to the syntactic features in the browser environment

I. Principle of compilation

As we all know, we write in a language that machines “don’t understand,” and we need to translate our code into a language that machines can understand (binary files).

The languages we write are collectively called programming languages. (There is no lack of individual tycoon direct machine language)

According to the different “translation” time can also be subdivided into compiled language, interpreted language.

  1. Compiled languages need to be “translated” (compiled) before the code is run, and are compiled to keep the machine-readable binaries so that each subsequent run can run the binaries directly without having to recompile them. Common compiled languages include C, C++, C#, Java, etc.
  2. Interpreted languages are also called scripting languages. In each run to do “translation”, a bit of “simultaneous interpretation” inside. Common interpreted languages include Python, VBScript, ActionScript, and so on.

The compiler acts as a “translator” between the compiled language and machine code. Compiling is a complex process, including lexical analysis, syntax analysis, semantic analysis, performance optimization, and executable file generation. Here do not delve into, “principle of compilation” study is not solid enough, dare not explore at will.

Author: @ month boat

The “translation” of interpreted languages and machine code is often called an interpreter. Before executing code, ensure that an interpreter is installed in the environment. Basically, there are four steps: lexical analysis, syntax analysis, semantic analysis, and interpretation.

Author: @ month boat

How JavaScript works

This section only discusses the operation at the language level, and does not explore the underlying mechanism. If you are interested in the underlying mechanism, please refer to the previous article EventLoop and Microtask macro Task from the Browser Principle.

First, JavaScript is often classified as an interpreted language.

In fact, however, running JS in modern browsers involves a compiler, but it doesn’t generate binaries that can be executed anywhere, and the compilation usually happens a few subtlety before or while the code is running. Note that this is not required by JavaScript or TC39, but was introduced by the Mozilla and Google developers to improve browser performance.

How does the interpreter compiler work in js

We all know that JS code runs in v8

  1. Generating an Abstract Syntax Tree (AST)
    • Tokenize, also known as lexical analysis, breaks down lines of source code into tokens (the smallest single character or string that is grammatically impossible to subdivide)
    • Parse, also known as syntax analysis, converts the token data generated in the previous step into an AST based on syntax rules. If there is a syntax error in the source code, this step is terminated and a syntax error is thrown
  2. Ignition generates bytecode based on the AST.
  3. In addition to generating the bytecode, executing Ignition is also responsible for interpreting the bytecode. The first time a piece of bytecode is executed, Ignition interprets it.

Some of you are looking at this and saying, “All done, where’s the compiler?”

Hold your horses and let me explain

JIT

Let me just throw a picture

TurboFan is the compiler for you.

Ignition interprets and executes bytecode.

  • If a piece of code is found to be executed multiple times, that piece of code is called HotSpot.
  • TurboFan steps in and compiles the bytecode directly into machine code, which is a binary file that can be executed directly
  • Therefore, when this hot code is executed again, the compiled machine code will be executed directly, and there is no need to “translate” bytecode into machine code, which greatly improves the efficiency of code execution.

This technique, called just-in-time compilation (JIT), is why some people say that the longer V8 code takes to execute, the more efficient it is

A bonus: a piece of code is called warm if it is performed more than once. It gets warmer when it is performed many times. It is called HotSpot when it is hot or hotter.

In a nutshell: When the V8 starts Ignition, the code bits get warm and hot as it runs, while TurboFan boosts engine efficiency.

Execution context and scope

Execution context

The execution context, as the name implies, is the environment in which the code is executed. The main function is to track the execution of the code.

Execution contexts can be roughly divided into three categories:

  1. Global context: An execution context created to run the body of code, for any code that exists outside of JavaScript functions. Destroy after the page is closed.
  2. Function context: Each function creates its own execution context as it executes. This context is commonly referred to as the local context. The function is destroyed after execution.
  3. Eval context: An execution context created using the eval() function.

Each context is pushed into the execution context stack as it is created. When exiting, it is removed from the context stack.

  • When the code starts to run, the global context is created.
  • When a function needs to be executed, the execution context of the function is created before execution begins and is pushed into the execution context stack.
  • When another function or block of code (ES6) is called from within a function, the current executable context is suspended, a new execution context is created, and pushed onto the stack.
  • When the current block finishes executing, the context stack pops up and the last suspended context continues executing. Exit the stack after the execution.
  • Code execution is completed, the main program exits, and the global execution context is popped from the execution stack. At this point, all the context on the stack has been popped, and the program is finished.

(Note: The executable context at the top of the execution context stack is called the Running Execution Context)

The content of the execution context is different in ES3, ES5, and ES9

ES3
  • Variable object: An object that stores variables.
  • Scope: Scope, also often referred to as a scope chain.
  • this
ES5
  • Variable environment: Used when declaring variables. This environment also contains an external reference to the external execution context, which we call the outer environment.
  • Lexical environment: lexical environment, used when retrieving variables.
  • this
ES9
  • Variable environment: Used when declaring variables.
  • Metonymy: lexical environment, used when retrieving a variable or this value.
  • Code Evaluation State: Used to restore the code execution location.
  • Realm: The base library and built-in object instances used
ES9 extra content
  • Function: Used when the task is a Function, indicating the Function being executed.
  • ScriptOrModule: used when the task is a ScriptOrModule to indicate the code being executed.
  • Generator: Only the Generator context has this property, indicating the current Generator

scope

Scope is an abstract concept that refers to the extent to which variables and functions are accessible within the context of their execution. The function of isolating variables and functions makes variables and functions of different scopes independent from each other.

The specific implementation mechanism is lexical encironment, which is implemented by scope in ES3. Its main function is to track the mapping relationship between identifiers and specific variables.

As we know from the previous section, lexical encironment is stored in the execution context, so scope can also be seen as a component of the execution context.

Js code execution requires syntax/lexical analysis, Ignition interpretation execution /TurboFan compilation execution. During the analysis phase the scope is determined, the execution context is created prior to execution, and the scope (lexical environment) information is saved.

In other words, the scope is determined when the code is written; This scope is also called a lexical scope. (Note: The opposite is dynamic scope, whose scope is determined at run time.)

Scope classification:

  • Global scope
  1. Outermost functions and variables defined outside them have global scope
  2. All variables that end up with a direct assignment are automatically declared to have global scope
  3. All properties of window objects have global scope
  • Block-level scope (introduced by ES6)
  1. Inside a function
  2. Inside a code block wrapped in a pair of curly braces
  • Function scope (pre-ES6) A variable or function defined within a function and accessible only within the function itself. At the end of function execution, variables defined inside the function are destroyed.

A scope chain is a nesting of scopes. When the current scope accesses less than one variable, it searches up the hierarchy of scopes. Look at a little chestnut

var name = "Bob";
function foo() {
  console.log(name);
}
function bar() {
  var name = "Ben";
  foo();
}
bar(); // Bob
Copy the code

Just a quick analysis of this example

  • Call bar, which declares a name variable with the value “Ben”, and call foo
  • Foo outputs the value of the name variable. Foo cannot find the name variable, so it looks up the scope chain
  • Since JS is lexically scoped, the scope above Foo is global, not the scope of the bar function
  • Find the name variable with the value “Bob” in the global scope and print it out

Modify scope

It is not easy to change the scope; after all, js is a lexical scope, which is defined at the time of writing. However, we can still modify the scope with two evEL functions and with.

eval

function show(execute) {
  eval(execute);
  console.log(str);
}

var str = "hello world";
var execute = 'var str = "hello javaScript"';

show(execute); // hello javaScript
Copy the code

In the example above, if you had not executed the eval function, the output would undoubtedly have been Hello World. After executing the eval function, the actual show function would have looked like this

function show() {
  var str = "hello javaScript";
  console.log(str);
}
Copy the code

The show function has more STR variables, so it prints the STR inside the show function instead of the global STR variable.

with

The with syntax makes it easier to read properties in an object (before ES6’s deconstruction), and it also creates a scope.

function change(animal) {
  with (animal) {
    say = "moo"; }}var dog = {
  say: "bark".size: "small"};var bull = {
  size: "big"}; change(dog); change(bull);console.log(dog.say); // moo
console.log(say); // moo
Copy the code

This example will report an error when run in strict mode, so take a quick look at this example

  • When executing change(dog), the change function creates a “with” scope in which the variables say and size are assigned. The “with” parameter is passed by reference. So dog-say went from “bark” to “moo.”
  • When executing change(bull), the change function also creates a “with” scope in which the variable is only size. However, in the “with” mode, an assignment to a non-existing variable “say” becomes a global variable. In non-strict mode, an undeclared variable becomes a global variable. Added a new say variable in global scope with the value “moo”.

In practice, use eval and with with caution.

Temporary dead zone

Talk about temporary dead zones. This concept was introduced with the let const declaration in ES6. Look at this example

function do_something() {
  console.log(bar); // undefined
  console.log(foo); // ReferenceError
  var bar = 1;
  let foo = 2;
}
Copy the code

As we all know, var declaration variables have variable promotion, bar declaration will be promoted, equivalent to the following code

function do_something() {
  var bar;
  console.log(bar); // undefined
  console.log(foo); // ReferenceError
  bar = 1;
  let foo = 2;
}
Copy the code

The bar variable has been declared but not assigned, so undefined is printed.

A reference error is reported when foo is accessed, indicating that there is no variable promotion for the let, or that Foo is in a temporary dead zone from the top of the block to initialization. Const similarly.

Three, prototype and inheritance

Prototypes and prototype chains

In JS, there is only one structure, that is the Object, including function is only a function Object, at the same time, Object Object is the “ancestor” of all objects.

Each instance object in turn has a private property (called __proto__) that points to its constructor’s prototype. The prototype Object of an instance’s constructor points to its constructor’s prototype, and so on, until its __proto__ is null. A complete “link” is formed, known as the prototype chain.

The following is the MDN definition of prototype chain:

Each object has a stereotype object, from which it inherits methods and properties. A stereotype object may also have stereotypes from which it inherits methods and properties, layer by layer, and so on. This relationship is often called a prototype chain.

It’s a little convoluted, but let me give you an example

const a = {
  name: "Zhang".gender: "Male".say: function () {
    console.log(`I am The ${this.name}`); }};const b = {
  name: "Bill".gender: "Female".say: function () {
    console.log(`I am The ${this.name}`); }};Copy the code

👆 in this example, we declare two objects. A and B store the personal information of Zhang SAN and Li Si, respectively. The __proto__ of a and b objects refers to the prototype of their constructor, in this case to the Object constructor

Going back to the example itself, this is not a very elegant way to do it, and if we want to add another five, we have to write name and gender again, which is a little tedious. In other programming languages (e.g. C++, Java), it is common practice to declare a class to solve this problem, but our js has no class. (ES6’s classes are just syntactic sugar; they are essentially constructors.)

It is common to use constructors in JS to emulate classes and instantiate them with the new operator.

The role of new:

  • Point the instance’s __proto__ property to the constructor’s prototype property
  • Bind the internal this to the instance object.

Ok, now modify the above example again

function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}

// Do not put a Person to prevent it from being assigned every time it is created
Person.toString = function () {
  console.log("I am a person");
};
Person.prototype.say = function () {
  console.log(`Hi! I am The ${this.name}`);
};

Class class Person {constructor(name, gender) {this.name = name; this.gender = gender; } static toString() { console.log('I am a person'); } say() { console.log(`Hi! I am ${this.name}`); }} * /
const a = new Person("Bill"."Female");
const b = new Person("Zhang"."Male");

a.say(); // Hi! I am li si
b.say(); // Hi! I am zhang SAN
Copy the code

We have a Person function, and we instantiate the Person using new.

  • The new operator is followed by the constructor, which is Person in this case
  • A and B are both instances of Person
  • The private __proto__ property in a and B refers to the prototype property of Person
  • Because Person’s prototype property is an Object, its __proto__ refers to the Object constructor’s prototype property

When we access the say method, we first look in the instance, and obviously there’s only name and gender in the instance object. So, will go to the prototype object of the instance, find the say method, call directly. Of course, if you can’t find it in the prototype, you’ll look for the prototype of the prototype.

The diagram is as follows:

summary

So let’s summarize

  • Hierarchy:

    • Objects have a private __proto__ attribute (nonstandard, implemented by the browser)
    • The constructor contains two private properties, prototype and __proto__, related to the prototype
    • The __proto__ property contains two private properties: constructor and __proto__ (Object’s __proto__ is null).
  • Relationship:

    • The object’s __proto__ points to the constructor’s prototype
    • The __proto__ of the prototype function refers to the prototype function of the preceding constructor, forming the prototype chain
    • The top layer is the prototype of the Object constructor, whose __proto__ eventually points to null

Inheritance in JS

Now Sam and Sam grew up and began to work, With Sam becoming a teacher and Sam becoming an outlaw fanatics.

👇 Here we create two new classes, Teacher and OutLaw, and use parasitic combination inheritance to implement the inheritance of Person. (A simplified version of es6 extends)

/ / inheritance
function extend(child, parent) {
  // The prototype of the subclass constructor refers to the prototype of the superclass constructor and inherits the methods of the superclass constructor
  child.prototype = Object.create(parent.prototype);
  child.prototype.constructor = child;
  // The proto of the subclass constructor points to the superclass constructor and inherits the static methods of the superclass
  child.__proto__ = parent;
}

/ / the teacher
function Teacher(name, gender, lesson) {
  // The subclass constructor calls the superclass constructor, inheriting the properties of the superclass
  Person.call(this, name, gender);
  this.lesson = lesson;
}

extend(Teacher, Person);

// Override the say method with the attribute Shadowed
Teacher.prototype.say = function () {
  console.log(`Hi! I am a The ${this.lesson} teacher`);
};

// Outlaw fanatics
function OutLaw(name, gender) {
  Person.call(this, name, gender);
}
extend(OutLaw, Person);
OutLaw.prototype.say = function () {
  console.log("Abba abba abba...");
};

const a = new Teacher("Bill"."Female"."English");
const b = new OutLaw("Zhang"."Male");

a.say(); // Hi! I am a English teacher
b.say(); // Abba abba Abba...
Copy the code

Combine the following figure with the comments

👇 ES6

class Person {
  constructor(name, gender) {
    this.name = name;
    this.gender = gender;
  }
  static toString() {
    console.log("I am a person");
  }
  say() {
    console.log(`Hi! I am The ${this.name}`); }}class Teacher extends Person {
  constructor(name, gender, lesson) {
    super(name, gender);
    this.lesson = lesson;
  }
  say() {
    console.log(`Hi! I am a The ${this.lesson} teacher`); }}class OutLaw extends Person {
  constructor(name, gender) {
    super(name, gender);
  }
  say() {
    console.log("Abba abba abba..."); }}const a = new Teacher("Bill"."Female"."English");
const b = new OutLaw("Zhang"."Male");

a.say(); // Hi! I am a English teacher
b.say(); // Abba abba Abba...
Copy the code

performance

Finding properties on the prototype chain is time-consuming and has a performance side effect, which is important in performance-demanding situations. In addition, attempts to access properties that do not exist traverse the entire stereotype chain.

Every enumerable property on the stereotype chain is enumerated as you iterate through the properties of an object. If you just want to check if an Object has its own defined property, rather than a property on its prototype chain, you can use the hasOwnProperty method inherited from Object.prototype to avoid having to look up the entire prototype chain if no property is found.

function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}
const a = new Person('Joe'.'male');

for(key in a) {
  if(a.hasOwnProperty(key)) {
    const ele = a[key];
    // do something}}Copy the code

Afterword.

If you have any other comments, please feel free to discuss them in the comments section. The article is also posted in the personal public account, welcome to pay attention to MelonField in-depth JS core syntax

Reference:

  • www.yuque.com/suihangadam…
  • www.ruanyifeng.com/blog/2011/0…
  • www.jianshu.com/p/6dd0e22ff…
  • Developer.mozilla.org/zh-CN/docs/…
  • Developer.mozilla.org/zh-CN/docs/…
  • Developer.mozilla.org/zh-CN/docs/…
  • Developer.mozilla.org/zh-CN/docs/…