Difficulties in JavaScript from the perspective of direction

Difficulties in JavaScript from the perspective of direction

Preface

Before we start writing, let's first understand direction: direction refers to the target direction and the direction it is facing.

Many people who have just started working with front-end or even some "old" front-end developers often get lost in the so-called difficulties in JavaScript, such as this, prototype, inheritance, closure, etc. This article will summarize my own understanding of these concepts in JavaScript through pointing and share them with you, hoping to help you better understand these so-called difficulties.

1. this

What is this? In fact, it is a kind of direction. This direction can be divided into the following situations

  • Normal call, this points to the caller
  • call/apply call, this points to the current thisArg parameter
  • Arrow function, this points to the this point of the current function

How do you understand this? I will explain them one by one.

1. Normal call

In simple terms, whoever calls it, this will point to it. There are several situations here, namely

1.1. Calling object methods

That is, a method is a property of an attribute on an object. Normally, when the method is called, this points to the object on which the method is mounted. Without further ado, it may be better to understand it by looking at the code directly.

var obj = {
  a: 'this is obj',
  test: function () {
    console.log(this.a);
  }
}
obj.test();
// this is obj

1.2. "Pure" function call

That is, the function is its own independent function, not a property attached to an object (except window), and it will not be used as a constructor, but only as a function. At this time, this points to the window object. The following is an example

var a = 'this is window'
function test () {
  console.log(this.a);
}
test();
// this is window

Let's understand this. It's actually very simple. We all know that the window object is a global object. In fact, the entire code block is equivalent to

window.a = 'this is window'
window.test = function test () {
  console.log(this.a);
  // At this time, window is the caller, that is, this will point to window
}
window.test();

1.3. Constructor call

That is, the function is called as a constructor, and this at this time points to the instance object of the constructor function. Let's take a look at an example. First, an example belonging to the second case

function test () {
  this.a = 'this is test';
  console.log(this.a);
  console.log(this);
}
test();
// this is test
// Window {}

According to the above understanding, this does point to the window object at this time, but if I change the form and call it with a constructor, what will be the result? Let's go directly to the code

function Test () {
  this.a = 'this is test';
  console.log(this.a);
  console.log(this);
}
var test = new Test();
// this is test
// Test {a: 'this is test'}

OK, it seems that there is indeed no problem. At this time, this does point to the instance object of the constructor. I will explain some of the specific explanations here in detail later in the prototype chain inheritance.

2. Call/apply

2.1、call

call method form, fun.call(thisArg[, arg1[, arg2[, ...]]])

  • thisArg, the current this points to
  • arg1[, arg2[, ...]], the specified argument list

For more details, please click MDN

The sample code is as follows

function Test () {
  this.a = 'this is test';
  console.log(this.a);
  console.log(this);
}
function Test2 () {
  Test.call(this);
}
var test = new Test2();
// this is test
// Test2 {a: 'this is test'}

2.2, apply call

Similar to call, the only obvious difference is that call takes multiple parameters, while apply takes two parameters, and the second parameter is in array or array-like form, fun.apply(thisArg, [argsArray])

  • thisArg, the current this points to
  • An array or array-like object, the array elements of which will be passed as separate parameters to the fun function

For more details, please click MDN

However, the array parameters in apply will eventually be converted into the parameter form of the call method, and then go through the following steps, which is why call is faster than apply. Here is an article with details, click the link.

In addition, when we talk about call/apply, how can we not mention bind? The this pointer in bind will always point to the current thisArg bound, that is, the context parameter cannot be overwritten. This is why a.bind(b).call(c), the final this pointer will be b. As for why, it is actually implemented through closures and in conjunction with call/apply. For details, please refer to the usage of bind in MDN and the Polyfill implementation.

3. Arrow Function

The first thing we need to introduce is that the arrow function itself does not have its own this bound to it. Its this points to the this of the current function. How to understand it? Let's take a look at the code directly.

function test () {
  (() => {
    console.log(this);
  })()
}
test.call({a: 'this is thisArg'})
// Object {a: 'this is thisArg'}

From this perspective, the understanding of the call/apply call above seems to be fine. What if I set a timer? Will this point to the Window global object? The answer is definitely no, because of the special nature of this in the arrow function, it will still point to the this of the current function. No more BB, just look at the code

function test () {
  setTimeout(() => {
    console.log(this);
  }, 0)
}
test.call({a: 'this is obj'})
// Object {a: 'this is obj'}

Of course, if a normal function uses setTimeout, this will point to the Window object. The demo code is as follows

function test () {
  setTimeout(function () {
    console.log(this);
  }, 0)
}
test.call({a: 'this is obj'})
// Window {...}

This may involve some points about setTimeout, I won’t go into details here, if you want to know more, click here

There are some special points in the arrow function. Here we only mention this point. Others, such as not binding arguments, super(ES6), or new.target(ES6), are the same as this. They will find the arguments of the current function.

There is also a detailed introduction to this in the arrow function. If you want to learn more, you can read it yourself

  • English original version (need to climb over the wall)
  • Chinese translation

2. Prototype/Prototype Chain

In fact, when we see prototype/prototype chain, we can associate it with inheritance. Let's separate the two and explain them separately. First, let's ask ourselves, what is a prototype? What is a prototype chain?

  • Prototype: Every function has a prototype property.
  • Prototype chain: Every object and prototype has a prototype. The prototype of the object points to the prototype object, and the prototype of the parent points to the parent of the parent. These prototypes connected layer by layer form a prototype chain.

It seems a bit confusing, but a picture can explain everything.

So how does this thing relate to the concept of pointing? In fact, one point that needs to be mentioned here is also a point in the screenshot above, that is, __proto__. I like to call it a prototype pointer. After all, prototype is just an attribute. It has no practical significance. In the end, prototype chain inheritance is still completed through the __proto__ prototype pointer. The so-called inheritance we see is nothing more than mounting the attributes to be inherited to the prototype attribute of the inheritor. When actually looking for inherited attributes, the __proto__ prototype pointer will be used to search up layer by layer, that is, it will look for a pointer to the __proto__ prototype pointer. Take a look at a demo

function Test () {
  this.a = 'this is Test';
}
Test.prototype = {
  b: function () {
    console.log("this is Test's prototype");
  }
}
function Test2 () {
  this.a = 'this is Test2'
}
Test2.prototype = new Test();
var test = new Test2();
test.b();
console.log(test.prototype);
console.log(test);

The execution results are as follows

I won't mention more about inheritance here, I will explain it in detail in the inheritance chapter. So that's all the "single" points about prototypes/prototype chains.
Summary: Prototype is just an attribute of all functions. The real "boss" is __proto__. Whoever the "boss" points to has the right to speak (of course, it may be because the "boss" is too overbearing, so it was standardized after ECMA-262).

3. Inheritance

Regarding inheritance, I have previously written a blog post summarizing some of the mainstream inheritance methods. If you want to learn more, please click on the portal. Here we will re-understand inheritance by pointing to this concept. Here we will talk about two inheritance methods that are always the same, one is constructor inheritance, and the other is prototype chain inheritance.

1. Constructor inheritance

In fact, it is the call/apply mentioned above, which changes the this pointer to thisArg. See the explanation above for details. Here is the code directly

function Test () {
  this.a = 'this is test';
  console.log(this.a);
  console.log(this);
}
function Test2 () {
  Test.apply(this)
  // or Test.apply(this)
}
var test = new Test2();
// this is test
// Test2 {a: 'this is test'}

2. Prototype chain inheritance

Generally, when we do prototype chain inheritance, we make the prototype property of the subclass equal to (point to) the instance of the parent class.

Child.prototype = new Parent();

So how does this approach implement prototype chain inheritance?

First of all, before explaining inheritance, we need to get to a point, that is, the object {} has some properties inside it. Here we can see the picture directly

As shown in the figure above, we can see that the properties of the object { } itself are the __proto__ prototype pointer and some methods mentioned above.
Next, let me talk about what the new keyword does. The process is roughly divided into three steps, as follows

var obj= {}; // Initialize an object obj
obj.__proto__ = Parent.prototype; // Point obj's __proto__ prototype pointer to the prototype property of the parent class Parent Parent.call(obj); // Initialize the Parent constructor

From what we can see here, I believe everyone can understand why I said above that __proto__ is the real "big brother".

Here I would like to mention an additional "high-end" thing we often do, which is to do monkey patching through the prototype. That is, I want to complete some independent operations while inheriting the parent class method. The specific code is as follows

function Parent () {
  this.a = 'this is Parent'
}
Parent.prototype = {
  b: function () {
    console.log(this.a);
  }
}
function Child () {
  this.a = 'this is Child'
}
Child.prototype = {
  b: function () {
    console.log('monkey patch');
    Parent.prototype.b.call(this);
  }
}
var test = new Child()
test.b()
// monkey patch
// this is Child

This is a custom class that we inherit and rewrite. What will happen if we inherit and rewrite built-in classes like Array, Number, String, etc.? I have also written a blog post about this topic.

4. Closure

I have also summarized and shared about closures. I will not mention some simple things and concepts here. If you want to know more, you can click here. It is the same as the chapter on prototype chains. Here I will abandon some of the original views. Here I will still understand this concept by substituting it into reference.

Generally speaking, we understand closures as: "inner functions defined to access local variables within a function."

JavaScript language features, each function has its own execution context, that is, a specific context pointer.

The inner context can always access the variables in the outer context, that is, each time the inner scope can search up until it accesses the variable it needs to access. The following is an example

var a = 'this is window'
function test () {
  var b = 'this is test'
  function test2 () {
    var c = 'this is test2';
    console.log(a);
    console.log(b);
    console.log(c);
  }
  test2();
}
test();
// this is window
// this is test
// this is test2

However, if you access it in reverse, you cannot access it, that is, the variable access points to the opposite direction of the current context, and it is irreversible.

function test () {
  var b = 'this is test';
}
console.log(b); // Uncaught ReferenceError: b is not defined

Here we use a very common situation as an example, that is, a for loop with setTimeout asynchronous task, as follows

function test () {
  for (var i = 0; i < 4; i++) {
    setTimeout(function () {
      console.log(i);
    }, 0)
  }
}
test();

Looking at the example above, we all know that "the answer will print 4 times 4". So why is this the case? What if I want to print 0, 1, 2, 3 in sequence?

I believe many friends will say that it can be achieved by using closures. Yes, it can be achieved by using closures. So why does this happen?

Let me briefly mention it here. First of all, there are two points involved here. One is the synchronous task of the for loop, and the other is the asynchronous task of setTimeout. In the JavaScript thread, because JavaScript itself is single-threaded, this feature determines that its normal script execution order is carried out in the form of document flow, that is, from top to bottom and from left to right. Every time the script is executed normally, whenever an asynchronous task is encountered, it will be set to a task queue. Then after the synchronous task is executed, the asynchronous task in the queue task will be executed.

Of course, for different asynchronous tasks, the execution order will be different, depending on which dimension the asynchronous task belongs to. I will not go into detail about Event Loop here. If you want to know more, please click here

Back to the effect we want to achieve above, our general approach is to use closures to pass parameters. The code is as follows

function test () {
  for (var i = 0; i < 4; i++) {
    (function (e) {
      setTimeout(function () {
        console.log(e);
      }, 0)
    })(i)
  }
}
test();
// 0 -> 1 -> 2 -> 3

In the loop, the anonymous function will be executed immediately, and the current i in the loop will be passed in as a parameter, and it will be used as a pointer to the formal parameter e in the current anonymous function, that is, the reference to i will be saved and it will not be changed by the loop.

Of course, there is another common way to achieve the above effect, which is to return a function from a self-executing anonymous function. The code is as follows

function test () {
  for(var i = 0; i < 4; i++) {
    setTimeout((function(e) {
      return function() {
        console.log(e);
      }
    })(i), 0)
  }
}
test();

I won’t introduce more advanced closure writing methods here. If you want to know more, please search by yourself.

The article is almost over here

But I can't help it, it's really coming to an end. Here's a summary of the whole blog post

Summarize

First of all, basically the so-called difficulties involved in JavaScript are explained throughout this article by pointing to this concept. Of course, this is my personal understanding of JavaScript, and the ideas are for reference only. If there is anything wrong, you are welcome to point it out.

In fact, when I wrote this blog many times, I wanted to string all the knowledge points together for explanation, but I was afraid that the effect would not be good, so I broke them down one by one and also mixed them. How much you can understand depends on yourself.

<<:  Personal understanding of the stack in function calls

>>:  Android Network Security Configuration

Recommend

Summary of Zhihu promotion: 12 methods of marketing on Zhihu

1. Create topics that attract a lot of attention....

Thin, Pleasure World's Thinnest OPPO R5 Comprehensive Experience

For an electronic product, if one aspect of its p...

The boy lit four mosquito coils and was "poisoned". Are mosquito coils so toxic?

Audit expert: Gu Haitong Deputy Chief Physician, ...

Short video promotion tips for Huoshan, Kuaishou, Douyin, etc.!

Traffic is the focus of everyone's attention ...

How to write a hot note on Xiaohongshu?

Some time ago, in order to deeply experience the ...

How to plan an online promotion program? What are the online promotion channels?

As the Internet becomes more and more developed, ...

Can I just pick any router? Every household has its own unique situation!

Routers are the most important devices for Intern...

Apple's hardcore fans complain: software quality is declining

[[126181]] Marco Arment, who enjoys a high reputa...

Jinzhai Dabie Mountain Red Education Base

Jinzhai Dabie Mountain Red Education Base trainin...