JavaScript - the understanding of this and the use of call, apply and bind

Keywords: Javascript Attribute IE

In JavasScript, this is a magical keyword. Sometimes, we may see a lot of this and get dizzy. However, as long as we understand this, we don't believe that we will go around ourselves in the future. Here, I record my understanding of this and its related application.

I. Understanding this

This in Javascript always points to the object that invokes the method in which it is invoked. The value of this is usually determined by the execution environment of the function, that is, how the function is called. Each time the same function is called, this may point to different objects.

1. this in ordinary functions

The first code:

function fn(){
  console.log(this) ; //this points to the top-level object, the window object in the browser, and the global object in the node.
}
fn(); 

The second code:

var name = "Bob";
var obj = {
    name: "Dancy",
    fn: function() {    
         console.log(this); //this points to the obj object
         console.log(this.name); //Dancy
    }
}
 obj.fn(); 

The third code:

Array.prototype.add = function(arr) {
   console.log(this); // This points to the array [1, 2, 3]. Note that this is the object that calls this method, not Array.prototype.
};
[1, 2, 3].add();

2. this in the constructor
this in the constructor points to the newly created object itself.

function Person(obj) {
     this.name = obj.name;
     this.age = obj.age;
     this.height = obj.height;
     console.log(this); //Current new objects
}
var p1 = new Person({name: "Bob", age: 25, height: 166})
console.log(p1); //As above

In the code above, p1 creates an instance object for the new keyword, so why does this point to the created object itself in the constructor? In fact, it's about the new keyword. When we're working on an object of a new instance, new does a couple of things:

a. Create an object obj
 b. Change this pointing in the constructor to point to the current new object obj
 c. After we add properties such as name, age, etc., the constructor finally returns the object by default.

So, in new, the code for the constructor above is equivalent to

//Note: For understanding only
function Person(obj) {
    var newObj = {}; //This is the object pointed to by this constructor above.
    newObj.name = obj.name;
    newObj.age = obj.age;
    newObj.height = obj.height;
    return newObj;
}

3. There is no this in the ES6 arrow function

var name = "Bob";
var obj = {
    name: "Dancy",
    fn: () => { 
         console.log(this); //Top-level objects, in browsers, arewindowobject
    }
}
 obj.fn(); 

2. Understanding of call apply and bind

1. Basic concepts and functions

These three methods all inherit from Function.prototype, so all function functions can call these three methods to use.
Their roles are as follows:

Call and apply: Specify the direction of this within the function, and then call the function in the specified scope, which immediately executes the function.

Bind: Specifies the direction of this within the function, and then returns a new function. The bind method does not immediately execute a function and is incompatible with the lower version of IE (lower than IE9)

2. Connections and differences of call apply bind

Contact:

a. The first parameter of the three is the this direction of the specified function
 b. You can pass parameters when a function is called.

Difference:

a. call and apply automatically execute the called function, and bind returns a function, requiring additional () to execute the function.
b. call and apply are not compatible, and bind is not compatible with low-version IE browsers.
c. call and bind pass parameters to the function are written in the following order, while apply only has two parameters. The second parameter is an array, and the parameters passed for the function are written in the array.

Call parameters: call(this points to, arg1, arg2, arg3,... )
Apply parameters: apply(this points to, [arg1, arg2, arg3,... )
Bid parameters: bind(this points to, arg1, arg2, arg3,... )

Example:

var obj = {
    a: 2
}
function fn(a, b, c) {
    console.log(this.a) ; // 2
    console.log(a + b + c); // 6
}
fn.call(obj, 1, 2, 3);
fn.apply(obj, [1, 2, 3])
fn.bind(obj, 1, 2, 3)(); //Note that bind returns a function, so add () to execute

In the example above, we modify this inside the function fn to point to the object obj by calling apply bind. The three functions are the same, but they are different when passing parameters. When call and apply are used, the function fn is executed while changing the direction of this in fn, while the function fn is only returned by bind, which needs to be added if it is to be executed.

3. Application scenarios of call application and bind

1. Find the maximum number in the array

Javascript does not provide a way to find the maximum value in an array. Combining the application and Math.max methods inherited from Function.prototype, you can return the maximum value of an array.

Code:

var a = [2, 4, 5, 7, 8, 10];
console.log(Math.max.apply(null, a)); 
console.log(Math.max.call(null, ...a)); //There...yes ES6 New grammar, please Baidu query understanding
//This is equivalent to Math.max(...a)

2. Change the empty element of an array to undefined

The difference between null elements and undefined is that the forEast method of an array skips null elements, but does not skip undefined and null. Therefore, traversing the internal elements yields different results.

Code:

var a = [1, , 3];
a.forEach(function(index) {
   console.log(index); //1,3, skip empty elements.
})
Array.apply(null,a).forEach(function(index){
   console.log(index);    ////1,undefined,3, set the empty element undefined
})

3. Convert Array-like Objects

For example, arguments in functions are indefinite parameters. arguments are a class array object, but not an array. There is no method of arrays. If we want arguments to be able to use the method of arrays, call application will come in handy.

Code:

function sum(a, b, c) {
    var aArgs = Array.prototype.slice.call(arguments);
    console.log(aArgs); // [1, 2, 3]
}
sum(1, 2, 3);

Although arguments does not have an array method, its data structure is very similar to that of arrays. The underlying implementation of slice is realized by loops. Because slice comes from Array.prototype, all arrays can call this method, so the results are obtained internally by looping this. During loops, this points to the object that calls the function slice, that is, the current array, while our argum. Entries can also be looped like arrays, so the slice method of arguments can be implemented by pointing this inside slice to arguments.

4. Use bind in timers
The first parameter of the timer is a function. When the function needs to change its internal this direction, it can only use bind, because call and apply will execute the function while changing the function, and can not achieve the desired effect of the function parameter of the timer. Bid will return the function, which is exactly what the timer needs.

Code:

obj = { 
    index: 0, 
    time: 0 
} 
function fn() { 
    this.index ++; 
    this.time += 1000; 
    console.log(this)
}
setInterval(fn.bind(obj), 1000)

Implementation results:

5. Compatibility of writing a bind function

Code:

if (!Function.prototype.bind) {
    Function.prototype.bind = function(obj) {
        if (typeof this !== 'function') {
            throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
        }

        var aArgs   = Array.prototype.slice.call(arguments, 1), // The indefinite argument arguments is copied as an array, starting with the first parameter of bind being the direction of this, followed by the parameter of the function.
        that = this;

        return function() { // The result of bind is to return a function
            that.apply(obj, aArgs)
        };
    };
}

6. An example of inheritance

Code:

function Father(n) {
     this.name = n.name;
     this.age = n.age;
     this.length = n.length;
}
Father.prototype = {
     constructor: Father,
     getValue(value) {
          alert(value)
     }
}

function Son(n) {
     Father.call(this, n) //The Method of Implementing Subclass Inheritance by call
}

function C() { }
C.prototype = Father.prototype;
Son.prototype = new C();

var son = new Son({name: "son"})
son.getValue("2")

Posted by rickphp on Mon, 31 Dec 2018 12:48:08 -0800