Write at the beginning: This article is basically basic and does not cover the inheritance aspects of ES6, so it does not expand into functions and prototype chains in too much detail, and there is no “advanced” programming ideas such as parasitic combinatorial inheritance. Of course, for some basic concepts, certainly on the basis of self-understanding, speak as thoroughly as possible, after all, whether first-class citizens: function, or prototype chain (JS is also known as the language based on prototype), for JS this language is very important.

Functions in JS

First of all, we need to understand a fundamental concept. In the JS language world where everything is an object, functions are also a class of objects, but the status of such objects is much higher than other objects, that is, functions are objects that can be called, and such objects are called Function objects. Within functions, there are two categories: custom functions (those written by the individual) and system functions (the methods that come with Windows).

For system functions are commonly used window. EncodeURIComponent, window, decodeURIComponent, window. Atob, window. Btoa, window. The console. The log, window. Alert (among them Window can be omitted during code writing), etc. I’m sure all of these functions are used in the code at one point or another, but I probably didn’t pay attention to the details. Of course these things are really enough for understanding only.

However, for custom functions, we should be cautious and cautious, because they are different from system functions, they are given and created by us, we need to be “responsible” for them.

Three ways to write a function

To understand how a function is created, we first need to know how it is created. Let’s take a look at the three pieces of code in 🌰

demo1(); // Console. log(demo2) is designed to solve the problem of mutual recursion. // Prints undefinedfunction demo1() {
  console.log("demo1");
}
var demo2 = function() {
  console.log("demo2");
};
var demo3 = new Function("return console.log('demo3')"); demo2(); // If demo2 is used as a variable promotion, its call position should be followed by the declaration to override the promotion of the variable demo3(); // It is not recommended that the Function constructor use a string body, which prevents optimization of the JS engine and causes some other problems. The call location should follow the declaration.Copy the code

Well, by looking at the three ways in which functions are declared, we can generalize the meaning of functions (custom functions). A function is a special class of objects whose value is known when called, and what we call is the business logic inside the function. By calling one function after another, and combining the internal business logic of these called functions, one function after another is realized.

The return value case in a function

Every time the function is called, a default return value (undefined) is generated inside the function. When we specify a return value inside the function, this return value overrides the default return value (undefined).

Now let’s look at another 🌰 to deepen the understanding of the above sentence.

function demo1() {}
function demo2(a, b) {
  returna + b; } console.log(demo1()); // The output is undefined console.log(demo2(1, 2)); // When demo2 is called, pass in arguments 1 and 2. The final return value overrides the default return value through the internal logic of the function. The print result is 3.Copy the code

A constructor in a function

From the above 🌰, we should know at least two things. First, the value of a function is reflected by the call of the function. Second, the essence of the call of the function is the internal business logic of the call of the function.

Now, with these two points, we will have a deeper understanding of the meaning of constructors in functions. For OOP languages (object-oriented languages), like JAVA and C++, the concept of classes is derived from the concept of SOLID (object-oriented design) as to why there is a class. We are not going to expand the description here, we just need to know that the class is the unit template of the object. Objects in JAVA and C++ are instantiations of classes.

In the JS language world, although often referred to as the object of everything, but this and object-oriented or have a certain difference. JS is more like an object-based language. So before ES6, JS did not have the concept of class, of course, even the class in ES6 is just a syntactic sugar (ruan Yifong teacher ES6 book, in fact, there are differences between the two). After all, although the concept of class has not been well reflected in ES6 before, JS is an object-based language, and how can let go of the characteristics of class? Constructors in JS functions have this property.

So with the new class concepts in ES6, do we still need to know about constructors? To be clear, there are subtle differences between classes and constructors in ES6, but the vast majority of real-world scenarios do not affect how they are used. But even if we don’t delve into the nuances of the two. It is worth taking a closer look at constructors simply because their core ideas are the same. Once you know the constructors, you basically know the class in ES6.

Furthermore, a deeper understanding of constructors helps us to have a deeper understanding of functions. Let’s have a 🌰 combination explanation.

function Dog(name) {
  this.type = "animal"; this.name = name; // The meaning of this keyword inside a function is to "construct" the unit template property inside a function. // a non-mandatory rule that, in order to distinguish a normal function from a constructor, the first letter of the constructor's function name should always be capitalized. // In fact, every function can be considered a constructor, but there are some rules that must be followed by default. After all, the code is not only for function, but also for presentation. } var demo = new Dog("Small strong"); // when the dog constructor is executed and instantiated as demo2. We should all have noticed the difference between the new operator console.log(demo) and the normal function execution. // It prints: dog {type: "animal", name: "Small strong"}, which is the demo2 object and containstype, name attribute. // For constructors, it is generally not recommended to display the returned object, because the constructor already "constructs" the template property, there is no need to do so. // Even for TS (typescript), the new operator is syntactically constrained to only call void functions (no display returns:returnA)Copy the code

We now examine the internal logic of the new operator (keyword) according to 🌰 above:

  1. Create a demo object that inherits from dog. prototype
  2. Call the constructor Dog with the appropriate arguments and bind this to the newly created object, instantiating the Dog constructor as a Demo object.
  3. Execute the logical code inside the Dog constructor.

Since the new operator involves the prototype and the prototype chain, its implementation code will be left in the prototype chain to explain in detail. Even partial implementation scenarios of the constructors themselves (constructors inherit from other constructors) involve prototype chains, but we can try other ways of temporarily replacing the prototype chain case for now. Let’s look at one more 🌰.

functionDog(name) { Animal.apply(this); this.name = name; // This is a friend of the same sex.bind) // Changing the object to which this refers is formally equivalent to the inheritance of the prototype, without considering the prototype chain for the moment.function Animal(){
  this.type="animal"
}
var demo2 = new Dog("Small strong"); console.log(demo2); // The print result is similar to the above 🌰, that is, the internal constructor attribute is slightly different, but otherwise almost identical.Copy the code

A callback function within a function

Because JS is a single-threaded asynchronous language, in some cases JS will not wait for the current function to complete before executing another function in order not to block the thread. For example, common timer functions are handled by dedicated timer threads, while our Ajax requests and restful architecture are handled asynchronously.

Then there is the problem of practical application scenarios, as I mentioned before: the internal business logic of one function after another is assembled together to realize various functions. In the process of “assembly”, the order of assembly sometimes becomes a major factor affecting the function.

Combined with the fact that JS is a single-threaded asynchronous processing language, the callback function has emerged to solve this kind of need pain point (of course, if you want to synchronous callback is also no problem, but I personally think that in general synchronous callback is not meaningful), in ES6, ES7 we have a more elegant way. Examples are Promise, async/await. This is actually explained in the second browser rendering article, but we won’t go into it here, but simply take a look at the callback function called callback hell at 🌰.

function demo(name, callback) {
  setTimeout(function demo1() {
    console.log("demo1"); callback(name); // Although it looks a bit nondescript, the essence of the callback function should be reflected in the function. // The result is demo (); // The result is demo ()set(demo1, demo2, demo1) // Since THE release of ES6, the frequency of callback functions has been greatly reduced, which is sufficient for practical development as we know it. console.log("demo");
}
demo("demo2".function(str) {
  console.log(str);
});
Copy the code

Prototype and prototype chain

First, let’s be clear. I don’t draw, I don’t draw, I don’t draw. In fact, I myself do not like those more complicated image understanding, because every time I look, I feel my head is confused. Here I will gradually advance the essence of prototype and prototype chain through words.

Here we restate our previous point of view: JS is a prototype-based, everything object language world. There are two key words: prototype and object. Since they are the cornerstone of THE JS language, there must be a lot of silent “deals” between the two. When we understand the “racket” between them, WE believe that the prototype and the prototype chain will be able to thoroughly grasp.

In OOP (object-oriented) language world, objects are instantiated by different unit templates, and then, through the complex relations between objects, objects and unit templates, unit templates and unit templates, derived from inheritance, polymorphism, encapsulation and other relatively “advanced” design concepts. We don’t need to worry about any of this. We just need to understand that in an object-oriented design language, objects need a unit template.

A prototype object

The concept of unit templates also exists in JS, but it exists under a different name, that is, prototype objects. Now, one thing to keep in mind is that a prototype object is a unit template. For those who are still confused about the relationship between a unit template and an object, the relationship between an object and a unit template is the relationship between a model and a mold. A model is based on a mold, but not necessarily limited to a mold.

prototype

Now let’s take a look at how the prototype object is generated. Functions have a special property called prototype, and while we won’t get into __proto__, which is confusingly __proto__, we can make a distinction here: That is, prototype is a function (a class of more special objects) unique attributes, general objects do not exist in this attribute, and __proto__ is an object (including functions) outside of a “alternative”, said he is alternative because this attribute is not officially recognized, but is still supported by some browsers. As for the specific tangle of the two, we will talk about it later.

Going back to the prototype object, since the function has a special property called prototype, when the function “uses” that property, it “generates” a prototype object. Ok, now that we’ve figured out the relationship between objects, stereotypes, and prototype objects, let’s take a look at a little 🌰 to reinforce the impression.

function Demo() {
  this.x = "Self attribute"; } // Start by simply declaring a constructor that has its own property x demo.prototype. y ="Attributes added to the prototype"; Var demo1 = new Demo(); var demo1 = new Demo(); // Execute the constructor to generate a demo1 object console.log(demo.prototype); // Print the result as a prototype object based on the Demo function: {y:"Attributes added to the prototype", constructor: Æ’} the console. The log (not); // Prints the result of an object generated by the Demo constructor: Demo {x:"Self attribute"} console.log(demo1.y); // In general, demo1 does not have y property, so it should print undefined. // However, due to its prototype object, do not forget the model and mold analogy. So he first looks up the properties on the prototype object that corresponds to the Demo, so the printed result is a string: "Properties added on the prototype".Copy the code

constructor

From 🌰 above, we should see that the Demo function is printed with a default constructor value in addition to the y attribute. What about this constructor?

First appeared in print this constructor refers to the Object. The prototype. The constructor, not in the middle of a ES6 constructor. But there should be a connection between the two. Although inheritance in class is a syntactic sugar, almost all of the requirements encountered by prototypes and constructors can be solved by using classes in ES6.

(2). (3). (4). (4). (4)

function Demo() {
  this.x = "Self attribute"; } var demo=new Demo(); console.log(Demo.prototype.constructor); / / print the value for the Demo constructor itself. The console log (Demo) prototype) constructor = = = Demo); / / even in the midst of JS rendering engine, Demo function and Demo. Prototype. The constructor is completely equal. / / so Demo. The prototype. The constructor () and run the Demo function is exactly the same. // In the basic business scenario, this is all we need to know for now.Copy the code

The awkward __proto__

First of all, my real name system, do not recommend the use of __proto__, not to say from the beginning, JS official did not list its attributes to the standard, even though most browsers are still support __proto__ this attribute. The fact that there are more convenient alternatives for practical scenarios alone does not need to be explored, but the underlying concepts should be understood.

No more nonsense, go directly to 🌰

function Demo() {
  this.x = "Self attribute";
}
var demo = new Demo();
Demo.prototype.y = "Properties on prototype objects"; console.log(Demo.prototype); console.log(demo.__proto__); // The printed values are the corresponding prototype functions of the Demo constructor: {y:"Properties on prototype objects"Constructor: Æ’} console.log(prototype === demo.__proto__); // The Demo constructor corresponds to a prototype object that is exactly the same console.log(demo.__proto__) in the JS rendering engine as when the object Demo uses the __proto__ attribute; // Although functions are also objects, __proto__ on functions has almost no meaning, so in typescript, even a parsing error is reportedCopy the code

Prototype chain

Since the constructor has a corresponding stereotype object, what about the stereotype object? He actually has his own prototype object. Even the prototype object of his prototype object may be the corresponding prototype object of other constructors. Do you feel a little round, so let’s go to 🌰 to explain.

function Child() {
  this.name = "Son level";
}
function Parent() {
  this.family = "Family";
}
Parent.prototype.type = "The archetypal properties of the family."; // We add attributes to the prototype object corresponding to the Parent constructortypeChild.prototype = new Parent(); // We "point" the prototype object corresponding to the constructor Child to the constructor Parent. var demo1 = new Child(); var demo2 = new Parent(); // Instantiate the constructors Parent and Child respectively to generate demo1 and demo2. console.log(demo1); console.log(demo2); // Print demo1 and demo2 separately and find that both contain only their own attributes. console.log(demo2.type === demo1.type); // But both Demo2 and Demo1 are ownedtypeProperties: "Prototype property of the family", and in the JS rendering engine, the properties in both objects are exactly the same console.log(demo1.__proto__); console.log(demo2); // Both prints point to the object instantiated by the Parent constructor, Demo2, but this time they are not congruent in the JS rendering engine. // Although the values of the two data types are exactly the same, their reference addresses are different as complex data types. console.log(demo1.__proto__.__proto__); console.log(demo2.__proto__); // Both print values as constructor Parent prototype objects, which are also incongruent. __proto__ can be replaced by prototype // but in order to get a better understanding of __proto__, we use __proto__ as 🌰.Copy the code

From 🌰 above, we should have seen a chain structure of the prototype object, which is essentially a prototype chain. When we want to find a property on a Demo1 object, there is an active behavior of looking up the property layer by layer, just like the scope chain. If the outermost layer is not found, undefined is returned.

In fact, with the introduction of ES6, it is no longer necessary to study prototypes, prototype chains and functions, but we must understand the core idea of ES6. After all, inheritance and class in ES6 are syntactical sugar, and they are still designed based on prototypes and functions. We may never get involved in building a wheel, but every bit of programming we learn helps us improve our problem-solving skills. Let’s encourage each other.