Advanced front-end basics (5): a comprehensive interpretation of this

~

In the process of learning JavaScript, because we do not understand some concepts very clearly, but we want to remember them in some ways, it is easy to draw some biased conclusions for these concepts that are convenient for our own memory. .

What is more harmful is that some inaccurate conclusions are widely circulated on the Internet.

For example, in the understanding of this pointing, there is such a saying: whoever calls it, this points to. When I first started to learn this, I believed this sentence very much. Because in some cases, this understanding makes sense. However, I often encounter some different situations in development. A wrong call due to this can make me stunned for a whole day. At that time, I also checked the information and asked the Great God in the group, but I still couldn't figure out "what the hell did I do wrong". It's just because I have an inaccurate conclusion in my mind.

Therefore, I think there is a need for such an article to help you interpret this in an all-round way. Let everyone have a correct and comprehensive understanding of this.

Before that, we need to review the execution context.

In the previous articles, I mentioned the life cycle of the execution context in several places. In order to prevent you from not remembering, let's review it again, as shown below. Execution context life cycle

In the creation phase of the execution context, the variable objects are generated respectively, the scope chain is established, and the this point is determined. Among them, we have carefully summarized the variable object and the scope chain, and the key here is to determine the this point.

First of all, we need to draw a very important conclusion that must be kept in mind. The point of this is determined when the function is called. That is, the execution context is determined when it is created. Therefore, the this pointer in a function can be very flexible. For example, in the following example, the same function points to different objects due to different calling methods.

var a = 10;
var obj = {
    a: 20
}

function fn () { console.log(this.a); } fn(); // 10 fn.call(obj); // 20 

In addition, during the execution of the function, once this is determined, it cannot be changed.

var a = 10;
var obj = {
    a: 20
}

function fn () { this = obj; // 这句话试图修改this,运行后会报错 console.log(this.a); } fn(); 

1. This in the global object

Regarding the this of the global object, I mentioned it before when I summarized the variable object, it is a special existence. this in the global environment, pointing to itself. So it's also relatively simple, there aren't as many complex cases to consider.

// 通过this绑定到全局对象
this.a2 = 20;

// 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10; // 仅仅只有赋值操作,标识符会隐式绑定到全局对象 a3 = 30; // 输出结果会全部符合预期 console.log(a1); console.log(a2); console.log(a3); 

2. This in the function

Before summarizing what this points to in functions, I think we need to get a feel for the elusiveness of this in functions through some strange examples.

// demo01
var a = 20;
function fn() { console.log(this.a); } fn(); 
// demo02
var a = 20;
function fn() { function foo() { console.log(this.a); } foo(); } fn(); 
// demo03
var a = 20;
var obj = {
    a: 10, c: this.a + 20, fn: function () { return this.a; } } console.log(obj.c); console.log(obj.fn()); 

These examples need to take some time to feel carefully. If you don't understand what's going on for a while, don't worry, we'll analyze it bit by bit.

Before the analysis, let's jump straight to the conclusion.

In a function context, this is provided by the caller and is determined by how the function is called. If the caller function is owned by an object, then when the function is called, the internal this points to the object. If the function is called independently, the this inside the function points to undefined. But in non-strict mode, when this points to undefined, it will automatically point to the global object.

From the conclusion, we can see that in order to accurately determine the point of this, it is very important to find the caller of the function and distinguish whether he is an independent call.

// 为了能够准确判断,我们在函数内部使用严格模式,因为非严格模式会自动指向全局
function fn() {  'use strict'; console.log(this); } fn(); // fn是调用者,独立调用 window.fn(); // fn是调用者,被window所拥有 

In the above simple example, fn()as an independent caller, according to the definition, its internal this point is undefined. And window.fn()because fn is owned by window, the internal this points to the window object.

Then you have mastered this rule, and now go back and look at the three examples above, by adding/removing strict mode, then you will find that this has become less illusory, and there are traces to follow.

But we need to pay special attention to demo03. In demo03, the c property in the object obj is used this.a + 20to calculate. Here we need to be clear that {}a new scope will not be formed alone, so here this.a, because there is no scope restriction, it is still in the global scope. So this here is actually the window object pointed to.

Then let's modify the code of demo03, and you can think about what changes will happen.

'use strict';
var a = 20;
function foo () { var a = 1; var obj = { a: 10, c: this.a + 20, fn: function () { return this.a; } } return obj.c; } console.log(foo()); // ? console.log(window.foo()); // ? 
  • In actual development, it is not recommended to use this in this way;
  • The strict mode mentioned many times above needs to be taken seriously, because in actual development, the strict mode is basically adopted now, and the latest ES6 also supports strict mode by default.

Let's look at some easy-to-understand examples to deepen your understanding of the caller and whether it runs independently.

var a = 20;
var foo = {
    a: 10,
    getA: function () { return this.a; } } console.log(foo.getA()); // 10 var test = foo.getA; console.log(test()); // 20 

foo.getA(), getA is the caller, he is not called independently, it is owned by the object foo, so its this points to foo. As test()the caller, although he has the same reference as foo.getA, it is called independently, so this points to undefined. In non-strict mode, it automatically turns to the global window.

Modify the code a little, and everyone can understand it by themselves.

var a = 20;
function getA() { return this.a; } var foo = { a: 10, getA: getA } console.log(foo.getA()); // 10 

Inspiration, another one. Example below.

function foo() {
    console.log(this.a) } function active(fn) { fn(); // 真实调用者,为独立调用 } var a = 20; var obj = { a: 10, getA: foo } active(obj.getA); 

3. Use call and apply to display the specified this

JavaScript internally provides a mechanism that allows us to manually set the this pointer. They are call and apply. All functions have two methods. They function exactly the same except for slightly different parameters. Their first parameter is the object this will point to.

As shown in the example below. fn is not a method of the object obj, but through call, we bind this inside fn to obj, so we can use this.a to access the a property of obj. This is how call/apply is used.

function fn() {
    console.log(this.a); } var obj = { a: 20 } fn.call(obj); 

The parameters after call and appplay are all passed parameters to the function to be executed. The call is passed one by one, and the apply is passed in the form of an array. This is their only difference.

function fn(num1, num2) {
    console.log(this.a + num1 + num2); } var obj = { a: 20 } fn.call(obj, 100, 10); // 130 fn.apply(obj, [20, 10]); // 50 

This makes JavaScript very flexible because of call/apply. Therefore, call/apply has many useful scenarios. Briefly summarize a few points, and welcome everyone to add.

  • Convert array-like object to array
function exam(a, b, c, d, e) {

    // 先看看函数的自带属性 arguments 什么是样子的 console.log(arguments); // 使用call/apply将arguments转换为数组, 返回结果为数组,arguments自身不会改变 var arg = [].slice.call(arguments); console.log(arg); } exam(2, 8, 9, 10, 3); // result: // { '0': 2, '1': 8, '2': 9, '3': 10, '4': 3 } // [ 2, 8, 9, 10, 3 ] // // 也常常使用该方法将DOM中的nodelist转换为数组 // [].slice.call( document.getElementsByTagName('li') ); 
  • Modify this pointer flexibly according to your own needs
var foo = {
    name: 'joker',
    showName: function() { console.log(this.name); } } var bar = { name: 'rose' } foo.showName.call(bar); 
  • implement inheritance
// 定义父级的构造函数
var Person = function(name, age) { this.name = name; this.age = age; this.gender = ['man', 'woman']; } // 定义子类的构造函数 var Student = function(name, age, high) { // use call Person.call(this, name, age); this.high = high; } Student.prototype.message = function() { console.log('name:'+this.name+', age:'+this.age+', high:'+this.high+', gender:'+this.gender[0]+';'); } new Student('xiaom', 12, '150cm').message(); // result // ---------- // name:xiaom, age:12, high:150cm, gender:man; 

Simply explain to friends with object-oriented foundation. In the constructor of Student, with the help of the call method, the constructor of the parent is executed once, which is equivalent to copying the code in Person in Sudent, where this points to the instance object new from Student . The call method ensures that this points to the correct point, so it is equivalent to implementing inheritance. The constructor of Student is equivalent to the following.

var Student = function(name, age, high) {
    this.name = name; this.age = age; this.gender = ['man', 'woman']; // Person.call(this, name, age); 这一句话,相当于上面三句话,因此实现了继承 this.high = high; } 
  • Make sure that this pointer remains the same in transfers to other execution contexts

In the following example, what we expect is that when getA is called by obj, this points to obj, but the existence of anonymous function leads to the loss of this point. In this anonymous function, this points to the global, so we need to find some way to find Return the correct this pointer.

var obj = {
    a: 20,
    getA: function() { setTimeout(function() { console.log(this.a) }, 1000) } } obj.getA(); 

The conventional solution is very simple, which is to use a variable to save the reference to this. We often use this method, but we also need to use the knowledge mentioned above to determine whether this has been modified during transmission. If it has not been modified, there is no need to use it in this way.

var obj = {
    a: 20,
    getA: function() { var self = this; setTimeout(function() { console.log(self.a) }, 1000) } } 

The other is to use the closure and apply method to encapsulate a bind method.

function bind(fn, obj) {
    return function() { return fn.apply(obj, arguments); } } var obj = { a: 20, getA: function() { setTimeout(bind(function() { console.log(this.a) }, this), 1000) } } obj.getA(); 

Of course, you can also use the bind method that already comes with ES5. It has the same effect as the bind method I encapsulated above.

var obj = {
    a: 20,
    getA: function() { setTimeout(function() { console.log(this.a) }.bind(this), 1000) } } 

4. This on constructors and prototype methods

When encapsulating objects, we almost always use this, but only a few people understand the this point in this process. Even if we understand the prototype, we don't necessarily understand this. So this part, I think, will be the most important and core part of this article. Understand this, it will be of great help for you to learn JS object-oriented.

Combined with the following example, I throw a few questions in the example for everyone to think about.

function Person(name, age) {

    // 这里的this指向了谁? this.name = name; this.age = age; } Person.prototype.getName = function() { // 这里的this又指向了谁? return this.name; } // 上面的2个this,是同一个吗,他们是否指向了原型对象? var p1 = new Person('Nick', 20); p1.getName(); 

We already know that this is determined during the function call, so it is very important to understand what happened during the new process.

Calling the constructor through the new operator will go through the following four stages.

  • create a new object;
  • Point the this of the constructor to this new object;
  • Point to the code of the constructor, adding properties, methods, etc. to this object;
  • Return the new object.

Therefore, when the new operator calls the constructor, this actually points to the newly created object, and finally the new object is returned and received by the instance object p1. Therefore, we can say that at this time, the this of the constructor points to the new instance object, p1.

The this on the prototype method is easier to understand. According to the definition of this in the function above, p1.getName()the getName in it is the caller, and he is owned by p1, so this in getName also points to p1.

Well, everything I know about this has been summarized. I hope you can really learn something after reading it, and then give me a like ^_^. If you find anything wrong, please point it out in the comments and I'll fix it asap. Thanks in advance.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324834713&siteId=291194637
Recommended