[THE LAST TIME]this: call,apply,bind

Preface

The last time, I have learned

[THE LAST TIME] has always been a series that I want to write, aiming to build up and review the front end.

It is also to check and fill in the gaps and share the technology.

Welcome to comment on Tucao.

The series of articles are first published in the public account [full stack front-end selection], the author's article collection is detailed in Nealyang/personalBlog . All contents are tentative

To be reasonable, this article is a bit out of scale. To be exact, the content of this article is basically the foundation of the foundation, but often this kind of basic article is difficult to control well in verbosity and detail. Where I can't get there, I hope you can make more comments and corrections.

THE LAST TIME series

This

I believe that students who have used JavaScript library for development will not be unfamiliar with this. Although this is very common in development, it is not easy to really eat this. Even for some experienced developers, they should stop to think about it. If they want to write this clearly, they need to talk about the scope and vocabulary of JavaScript.

Misunderstanding 1 of this: this points to himself

function foo(num) {
  console.log("foo:"+num);
  this.count++;
}

foo.count = 0;

for(var i = 0;i<10;i++){
    foo(i);
}

console.log(foo.count);

By running the above code, we can see that foo function has been called ten times, but this.count does not seem to be added to foo.count. That is to say, this.count in the function is not foo.count.

Misunderstanding 2 of this: this points to its scope

Another misunderstanding of this is that it somehow points to the scope of the function. In fact, in a sense, it is correct, but in another sense, it is indeed a misunderstanding.

Specifically, this does not point to the lexical scope of a function in any way. The scope seems to be an object that takes all available identifiers as properties. Internally, it is right, but JavaScript code cannot access the scope "object" because it is an internal implementation of the engine.

function foo() {
    var a = 2;
    this.bar();
}

function bar() {
    console.log( this.a );
}

foo(); //undefined

This in the global environment

Since it is a global environment, of course, we need to clarify the concept of hosting environment. In short, a language needs an environment when it runs, and this environment is called the host environment. For JavaScript, the most common host environment is the web browser.

As mentioned above, we can also know that the environment is not unique, that is, JavaScript code can run not only in the browser, but also in other programs that provide the host environment. The other most common is Node. As the host environment, Node also has its own JavaScript engine: v8.

  • In browser, this is equivalent to window object in global scope.
  • In browser, declaring a variable with var is equivalent to adding attributes to this or window.
  • If you do not use var or let(ECMAScript 6) when declaring a variable, you are adding or changing the attribute value to the global this.
  • In a node environment, if you use REPL to execute a program, this is equal to global.
  • In node environment, if a js script is executed, this does not point to global but module.exports is {}.
  • In the node environment, in the global scope, if you execute a script file with REPL, declaring a variable with var will not add the variable to this as in the browser.
  • If you execute the script file directly instead of using REPL, the result is the same as in the browser.
  • In the node environment, when running script files with REPL, if var or let is not used when declaring variables, this variable will be automatically added to the global object, but not to this object. If the code is executed directly, it will be added to global and this at the same time.

This piece of code is relatively simple. We don't need to talk with the code. Let's talk with the diagram instead.

This in functions and methods

Many articles will distinguish functions from methods, but I think... There's no need. Let's see who ordered it. It's as cool as a mushroom.

When a function is called, an activity record is created, which also becomes the execution environment. This record contains information such as where the function is called, how it is called, and what parameters are passed. One of the properties of this record is the reference to this that will be used during function execution.

this in the function is variable, but the rules are invariant.

You ask this function: "sister oh, no, function!" Who ordered you? "

”It's him! "

So, this points to that guy! More academic, so! Normally! This is not determined at compile time, but the context execution environment bound at run time. This has nothing to do with the statement!

function foo() {
    console.log( this.a );
}

var a = 2;

foo(); // 2

Remember the above, who ordered me! =>Foo() = windwo. Foo(), so this executes the window object, which will print out 2.

Note that for strict mode, it is illegal to bind global objects by default, and this is set to undefined.

function foo() {
    console.log( this.a );
}

var obj2 = {
    a: 42,
    foo: foo
};

var obj1 = {
    a: 2,
    obj2: obj2
};

obj1.obj2.foo(); // 42

Although this xx was ordered more... However, we only ask the person who is ojb2, so the output of this.a is 42.

Attention, my point here! It's not what you think Oh, it's runtime~

This in constructor

Okay... This is Congliang.

As mentioned above, we don't look at where to define this, but at the runtime. The so-called constructor starts with the keyword new!

Who give me new? Who am I with?

In fact, the following things have been accomplished internally:

  • A new object will be created
  • The newly created object will be connected to the prototype chain.
  • This newly created object will be set as this binding of function call
  • Unless the function returns another object of its own, the function called by new will automatically return a newly created object
foo = "bar";
function testThis(){
  this.foo = 'foo';
}
console.log(this.foo);
new testThis();
console.log(this.foo);
console.log(new testThis().foo)//Self attempt

this in call, apply, bind

Okay... This is the bag

In many books, call, apply and bind are called the strong binding of this. Let's be clear. Who can help? Who can I help? As for the difference, realization and principle of the three, let's talk about it later.

function dialogue () {
  console.log (`I am ${this.heroName}`);
}
const hero = {
  heroName: 'Batman',
};
dialogue.call(hero)//I am Batman

The above dialogue.call(hero) is equivalent to dialogue.apply(hero)`dialogue.bind(hero)() '.

In fact, I explicitly specify what this is!

this in arrow function

This of the arrow function is somewhat different from the function in JavaScript. The arrow function permanently captures this value, preventing it from being changed later by apply or call.

let obj = {
  name: "Nealyang",
  func: (a,b) => {
      console.log(this.name,a,b);
  }
};
obj.func(1,2); // 1 2
let func = obj.func;
func(1,2); //   1 2
let func_ = func.bind(obj);
func_(1,2);//  1 2
func(1,2);//   1 2
func.call(obj,1,2);// 1 2
func.apply(obj,[1,2]);//  1 2

This value in the arrow function cannot be explicitly set. In addition, if you use call, apply or bind methods to pass values to this, the arrow function will ignore them. Arrow function refers to this value set by arrow function at creation time.

Arrow functions cannot also be used as constructors. Therefore, we cannot set this property in the arrow function.

this in class

Although whether JavaScript is an object-oriented language is still controversial. We don't argue here either. But as we all know, classes are a very important part of JavaScript applications.

Class usually contains a constructor, which can point to any newly created object.

However, as a method, if the method is called as a normal function, this can also point to any other value. As with methods, classes can lose track of receivers.

class Hero {
  constructor(heroName) {
    this.heroName = heroName;
  }
  dialogue() {
    console.log(`I am ${this.heroName}`)
  }
}
const batman = new Hero("Batman");
batman.dialogue();

this in the constructor points to the newly created class instance. When we call batman.dialogue(), dialogue() is called as a method, and batman is its receiver.

But if we store the reference of the dialogue() method and call it as a function later, we will lose the receiver of the method, at which time this parameter points to undefined.

const say = batman.dialogue;
say();

The reason for the error is that JavaScript classes are implicitly running in strict mode. We call the say() function without any auto binding. To solve this problem, we need to manually bind the dialogue() function with batman using bind().

const say = batman.dialogue.bind(batman);
say();

The principle of this

Keke, technical article, let's be serious

We all say that this refers to the environment in which the function runs. But why?

As we all know, an object in JavaScript is assigned an address to a variable. When the engine reads variables, it actually wants an address and then reads the object from the original address. So if the attribute in the object is also a reference type (such as function), of course, it is the same!

JavaScript allows the function body to reference other variables of the current environment, which are provided by the running environment. Because the function can be executed in different running environments, a mechanism is needed to provide the running environment for the function! And this mechanism is what we talk about. The original intention of this is to use it inside the function, referring to the current running environment.

var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// Separate execution
f() // 1

// obj environment execution
obj.f() // 2

obj.foo() finds foo through obj, so it is executed in obj environment. Once var foo = obj.foo, the variable foo points directly to the function itself, so foo() becomes executed in the global environment.

summary

  • If the function is called in new, if so, this is bound to the newly created object.
var bar = new Foo();
  • Whether the function is called through call, apply or other hard calls. If so, this is bound to the specified object.
var bar = foo.call(obj);
  • If the function is invoked in a context object, if so, this is bound to that context object.
var bar = obj.foo();
  • If not, use the default binding. If in strict mode, bind to undefined. Note that here is the strict declaration in the method. Otherwise, bind to global object
var bar = foo();

Small test knife

var number = 2;
var obj = {
  number: 4,
  /*Anonymous function self tuning*/
  fn1: (function() {
    var number;
    this.number *= 2; //4

    number = number * 2; //NaN
    number = 3;
    return function() {
      var num = this.number;
      this.number *= 2; //6
      console.log(num);
      number *= 3; //9
      alert(number);
    };
  })(),

  db2: function() {
    this.number *= 2;
  }
};

var fn1 = obj.fn1;

alert(number);

fn1();

obj.fn1();

alert(window.number);

alert(obj.number);

Leave your answers in the comment area~

call & applay

call, apply and bind have been mentioned above. The application defined in MDN is as follows:

The apply() method calls a function with a specified this value and parameters provided as an array (or array like object)

Syntax:

fun.apply(thisArg, [argsArray])

  • thisArg: this value specified when the fun function runs. It should be noted that the specified this value is not necessarily the real this value when the function is executed. If the function is in non strict mode, when it is specified as null or undefined, it will automatically point to the global object (window object in browser), and at the same time, this with the original value (number, string, Boolean value) will point to the auto wrapped object of the original value.
  • argsArray: an array or class array object in which the array elements are passed as separate parameters to the fun function. If the value of the parameter is null or undefined, no parameter needs to be passed in. Starting with ECMAScript 5, you can use class array objects. For browser compatibility, see the bottom of this article.

The difference is that the second parameter type passed in by apply and call is different.

The syntax of call is:

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

It should be noted that:

  • The object calling call must be a Function
  • The first parameter of call is an object. The caller of the Function will point to this object. If it is not transferred, it defaults to the global object window.
  • Starting with the second parameter, you can receive any parameter. Each parameter is mapped to the parameter of the Function in the corresponding location. However, if all parameters are passed in as an array, they will be mapped to the first parameter corresponding to the Function as a whole, and then the parameters will be empty.

The syntax of apply is:

Function.apply(obj[,argArray])

It should be noted that:

  • Its caller must be a Function and receive only two parameters
  • The second parameter, which must be an array or a class array, will be converted into a class array, passed into a Function, and mapped to the parameter corresponding to the Function. This is also an important difference between call and apply.

Memory skills: apply, a start, array, so the second parameter needs to transfer data.

Excuse me? What is a class array?

Core idea

Borrow!

Yes, it is. Here's a chestnut! I don't have a girlfriend. Weekends... Well, no, I don't have a motorcycle. It's a nice day at the weekend. I want to go out and bend. But I have no money! How to do it? Find a friend to borrow it. It has achieved the goal and saved money!

In the program, we can understand that an object does not have a method to achieve a function, but we do not want to waste memory overhead, so we borrow another object with the method to borrow it.

Frankly speaking, their core idea, including bind, is to borrow methods, which has achieved the purpose of saving costs.

Application scenario

The code is relatively simple, so I won't explain it.

  • Convert a class array to an array
const arrayLike = {
  0: 'qianlong',
  1: 'ziqi',
  2: 'qianduan',
  length: 3
}
const arr = Array.prototype.slice.call(arrayLike);

  • Find the maximum value in the array
var arr = [34,5,3,6,54,6,-67,5,7,6,-8,687];
Math.max.apply(Math, arr);
Math.max.call(Math, 34,5,3,6,54,6,-67,5,7,6,-8,687);
Math.min.apply(Math, arr);
Math.min.call(Math, 34,5,3,6,54,6,-67,5,7,6,-8,687);
  • Variable type judgment

Object.prototype.toString is used to determine the type, especially for reference types.

function isArray(obj){
  return Object.prototype.toString.call(obj) == '[object Array]';
}
isArray([]) // true
isArray('qianlong') // false
  • inherit
// Parent class
function supFather(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green']; // Complex type
}
supFather.prototype.sayName = function (age) {
    console.log(this.name, 'age');
};
// Subclass
function sub(name, age) {
    // Borrow the method of the parent class: modify its this point, assign the method and property in the constructor of the parent class to the child class
    supFather.call(this, name);
    this.age = age;
}
// Rewrite the prototype of the subclass, and fix the constructor point
function inheritPrototype(sonFn, fatherFn) {
    sonFn.prototype = Object.create(fatherFn.prototype); // Inherit the properties and methods of the parent class
    sonFn.prototype.constructor = sonFn; // Fix constructor to point to inherited function
}
inheritPrototype(sub, supFather);
sub.prototype.sayAge = function () {
    console.log(this.age, 'foo');
};
// Instantiate the subclass. You can find the properties and methods on the instance.
const instance1 = new sub("OBKoro1", 24);
const instance2 = new sub("Xiao Ming", 18);
instance1.colors.push('black')
console.log(instance1) // {"name":"OBKoro1","colors":["red","blue","green","black"],"age":24}
console.log(instance2) // {"name": "Xiaoming", "colors":["red","blue","green"],"age":18} 

THE LAST TIME may be written after inheritance. I don't know if it's necessary

Simple version inheritance

ar Person = function (name, age) {
  this.name = name;
  this.age = age;
};
var Girl = function (name) {
  Person.call(this, name);
};
var Boy = function (name, age) {
  Person.apply(this, arguments);
}
var g1 = new Girl ('qing');
var b1 = new Boy('qianlong', 100);

bind

Bind is the same as call/apply, but bind will return a new function! Not immediately! call/apply changes this of the function and executes it immediately.

Application scenario

  • Cache parameters

The principle is actually to return a closure. After all, bind returns a copy of a function.

for (var i = 1; i <= 5; i++) {
    // Cache parameters
    setTimeout(function (i) {
        console.log('bind', i) // Output in sequence: 1 2 3 4 5
    }.bind(null, i), i * 1000);
}

The above code is also a classic interview question, which will not be expanded.

  • this lost problem

When it comes to this loss, the most common is to define a method in react and then add bind(this) operation later. Of course, the arrow function doesn't need to, which we discussed above.

Handwritten implementation

apply

The first handwriting, let's do it step by step.

  • Triggered from definition because it is a function caller. So it must be to add a method to the function, and the first parameter is the future this context.
Function.prototype.NealApply = function(context,args){}
  • If context, this points to window
Function.prototype.NealApply = function(context,args){
    context = context || window;
    args = args || [];
}
  • Add a non overwritable key to the context and bind this

Yes, we don't have black magic. Since we bind this, we still can't escape the way we mentioned above.

Function.prototype.NealApply = function(context,args){
    context = context || window;
    args = args || [];
    //Add a unique attribute to the context to avoid overwriting the original attribute
    const key = Symbol();
    context[key] = this;//this here is a function
    context[key](...args);
}

In fact, this time we have been effective.

  • At this time, we have finished the implementation. We need to return the results and clean up the garbage generated by ourselves.
Function.prototype.NealApply = function(context,args){
    context = context || window;
    args = args || [];
    //Add a unique attribute to the context to avoid overwriting the original attribute
    const key = Symbol();
    context[key] = this;//this is testFun
    const result = context[key](...args);
    // Side effects of taking away
    delete context[key];
    return result;
}

var name = 'Neal'

function testFun(...args){
    console.log(this.name,...args);
}

const testObj = {
    name:'Nealyang'
}

testFun.NealApply(testObj,['Attention together',':','Full stack front end selection']);

The execution result is the screenshot above.

  • optimization

First, I don't say optimization is because I hope you can focus on the core, and then go to trim the edge! Rome wasn't built in a day. It's actually a little bit more perfect when you look at other people's codes.

The truth is that there are still many optimizations to be done. Here we need to optimize the context judgment:

    // Correct judgment of function context object
    if (context === null || context === undefined) {
       // The value of this specified as null and undefined automatically points to the global object (window in browser)
        context = window 
    } else {
        context = Object(context) // this with the original value (number, string, Boolean) points to the instance object of the original value
    }

Other optimization you can add a variety of user fault tolerance. For example, make a fault tolerance for the class array of the second parameter

    function isArrayLike(o) {
        if (o &&                                    // o is not null, undefined, etc.
            typeof o === 'object' &&                // o is the object.
            isFinite(o.length) &&                   // o.length is a finite value
            o.length >= 0 &&                        // o.length is non negative
            o.length === Math.floor(o.length) &&    // o.length is an integer
            o.length < 4294967296)                  // o.length < 2^32
            return true;
        else
            return false;
    }

Stop! I really don't want to be long winded any more. This article should not be so long.

call

Beggar version implementation:

//Pass parameters are changed from an array to pass parameters one by one. You can use arguments instead of... Extension operators.
Function.prototype.NealCall = function (context, ...args) {
    //In this case, you can use es6 to set the default parameters.
    context = context || window;
    args = args ? args : [];
    //Add a unique attribute to the context to avoid overwriting the original attribute
    const key = Symbol();
    context[key] = this;
    //Call function by implicit binding
    const result = context[key](...args);
    //Remove added properties
    delete context[key];
    //Returns the return value of a function call
    return result;
}

bind

The implementation of bind is more reasonable than that of apply and call. It is also a frequent interview test. Because we need to consider the copy of the function. But it's still relatively simple. There are many versions on the Internet, so we won't expand them here. Specifically, we can discuss it in groups~

Function.prototype.myBind = function (objThis, ...params) {
    const thisFn = this; // Store the source function and params (function parameter) above it
    // Pass parameters to the returned function secondParams twice
    let fToBind = function (...secondParams) {
        const isNew = this instanceof fToBind // Whether this is an instance of fToBind, that is, whether the returned fToBind is called through new
        const context = isNew ? this : Object(objThis) // The new call is bound to this, otherwise it is bound to the incoming objThis
        return thisFn.call(context, ...params, ...secondParams); // call the source function to bind the point of this and pass the parameter to return the execution result
    };
    if (thisFn.prototype) {
        // Copy the prototype of the source function to fToBind. In some cases, the function does not have a prototype, such as the arrow function.
        fToBind.prototype = Object.create(thisFn.prototype);
    }
    return fToBind; // Return copied function
};
Function.prototype.myBind = function (context, ...args) {
    const fn = this
    args = args ? args : []
    return function newFn(...newFnArgs) {
        if (this instanceof newFn) {
            return new fn(...args, ...newFnArgs)
        }
        return fn.apply(context, [...args,...newFnArgs])
    }
}

Last

Don't forget the examination questions of this above, classmate, it's time to hand in the paper!

Reference link

Exchange of learning

Pay attention to the public account: [front end selection of the whole stack] get the recommendation of good articles every day. You can also join the group and learn and communicate together.

Keywords: Javascript Attribute ECMAScript React

Added by majocmatt on Thu, 17 Oct 2019 05:30:55 +0300