Understand the mechanism of such a mysterious thing, the role of the first natural to deal with the interviewer, the role of the second is to maintain other people’s bad code ~

1. Pre-knowledge

1.1 A big misunderstanding of this

Many people have an unconscious misconception about this – that the value of this depends on where the function is declared

let obj = {
    a: function () {
        console.log(this);
    },
    b: function () {
        let f = obj.a;
        f();
    }
}

obj.b(); // window
Copy the code

Many people will mistake this for obj when they see a function declared inside an object

1.2 Function names are Pointers

A function name is a pointer to a function object

As an object inside a function, this is naturally tied to the function

However, many people don’t realize that the function name is a pointer

Consider the following code:

  1. Whether fn1 can directly find the position of its function body (ans: yes)
  2. Is fn1 still related to OBJ?
function fn() {
    console.log(this);
}
let obj = {
    a() {
        returnfn; }}let fn1 = obj.a();

fn1(); // window
Copy the code

If you can figure out the above two questions, you probably already have a feeling that this can refer to window

2 This mechanism is explained in detail

2.1 This is essentially the caller pointing to it

Javascript being an interpreted language, the value of this cannot be determined until the function is called.

What does that mean, like this code

function fn() {
  console.log(this);
}
Copy the code

Do you know what this in FN is now?

Impossible to know, because fn is not called!

So what do you mean you have to know until the function is called?

Let’s see what happens when we make a different call to fn above

function fn() {
    console.log(this);
}
let obj = {fn};

fn(); // window
obj.fn(); // obj
Copy the code

It is obvious that the value of this is different depending on how the call is made

**this essentially refers to its caller **.

Not so fast, the whole article is built around that quote

First, let’s analyze how many ways a function can be called, and then explain them separately:

  1. The global call
  2. The method call
  3. The new call

2.1 Global Invocation (Independent Invocation)

Whenever ** is called independently, you can infer that this inside the function is Windows **

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

foo(); // window
Copy the code

For foo(), foo is a function name, whereas in JS, a function name is just a pointer to the function name (), which stands alone. Javascript you Don’t Know is what the authors call a singlehander call, which makes this inside the called function bind to window by default

Combined with the statement that **this essentially refers to its caller, the essence of a global call is that window calls foo **

foo();
// Equivalent to the following
window.foo();
Copy the code

2.2 Method Invocation

Method (method)Refers to an object whose property is a function, such asobj = {fn:function(){}}And theobj.fn()calledThe method call

Combine this with the statement **this essentially refers to its caller ** :

After a method call, this within the method points to the object that owns the method

let obj = {
    fn() {
        console.log(this);
    }
}

obj.fn(); // obj
Copy the code

2.2.1 Proximity principle for method invocation

In the case of multiple nested objects, this refers to the object closest to which it was called

let obj = {
    a: {
        b: {
            fn() {
                console.log(this);
            }
        }
    }
}

obj.a.b.fn(); // obj.a.b
Copy the code

2.2.2 Compare with global invocation

Here’s a code that, without running it, many people will guess wrong

let obj = {
    a() {
        console.log(this); }}let fn = obj.a;

obj.a(); // obj
fn(); // window
Copy the code

Obj. A (); No doubt, the key is why is fn() window

Fn is a pointer to obj. A. Fn () is a global call, so this refers to window

2.3 new

The key is to remember what **new does ** :

  1. Create a temporary object. This points to the temporary object
  2. The instance of__proto__Point to the prototype of the class
  3. Return temporary object
function fn() {
    console.log(this);
}

new fn(); // The result is shown in the screenshot below
Copy the code

3. This in other scenarios

3.1 This in strict mode

In strict mode, only one point is required; otherwise, the same as in non-strict mode

This is undefined in functions in the global scope

function test() {
  "use strict"
  console.log(this) 
}
test() // undefined
Copy the code

So, when using a constructor, if you forget to add new, this no longer refers to the global object, but instead reports an error, because that is the global call to the function

let People = function (name) {
  "use strict"
  this.name = name
}
People() // Cannot set property 'name' of undefined
Copy the code

3.2 This in array

function fn() {
  console.log(this)
}

arr[fn, fn2, fn3]

arr[0] ()// ??

// answer:arr

/ / parsing
// Arrays are also a type of object
// arr[0]() = arr.0().call(arr)
Copy the code

3.3 Nested functions this

Note that wherever the function name () appears, it is called independently

A / / examples
function fn0() {
  function fn() {
    console.log(this);
  }

  fn();
}

fn0(); // this is the global variable in fn

2 / / examples
let a = {
  b: function () {
    console.log(this) // {b:fn}
    function xx() {
      console.log(this) // window
    }
    xx()
  }
}
a.b()
Copy the code

3.4 This in setTimeout and setInterval

This points to the global variable

document.addEventListener('click'.function (e) {
	console.log(this);
	setTimeout(function () {
		console.log(this); // this is the global variable
	}, 200);
}, false);
Copy the code

3.5 This in the event

The this in the event refers to the DOM node that triggered the event

document.querySelector('div').addEventListener('click'.function (e) {
	console.log(this) // <div></div>
})
Copy the code

3.6 Arrow function does not contain this

Many people mistakenly think that this in the arrow function is bound to the parent scope, but this is not true

The essence of this in the arrow function is simply to use this directly from the parent scope; it has no this of its own

Therefore, the call/apply binding of arrow functions is also invalid

let test = () = > console.log(this);

test.call('this'); // window
test.apply('this'); // window
Copy the code

4 Specify this

Personally, I think it is best to use call/apply/bind to force binding for this

4.1 Overview of Call/Apply and Bind

  1. We are going to change our mindcall/applyFall into one category **,bindPut them in a separate category
  2. What all three have in common is that you can specify this
  3. Call /apply and bind are both tied to the prototype Function, so any instance of Function can call these three methods
Function.prototype.call(this,arg1,arg2)
Function.prototype.apply(this,[arg1,arg2])
Function.prototype.bind(this,arg1,arg2)
Copy the code

4.2 Call /apply — The first argument is this

4.2.1 Functions of Call/Apply

There is only one difference between call and apply: the call() method takes several arguments, while the apply() method takes an array of arguments

Function:

  1. Call a function
  2. Change the function’s this pointer
  3. Pass parameters to a function

Return value The return value is the return value of the function you called

window.a = 1

function print(b, c) {
  console.log(this.a, b, c)
}

// call it independently
print(2.3) / / 1 2 3

// Use call and apply
print.call({a: -1}, -2, -3) // -1 -2 -3
print.apply({a: 0}, [...2, -3]) / / 0-2-3
Copy the code

4.2.2 Apply passes array parameters

Although apply passes an array of arguments, ** Apply actually breaks the array apart and passes it, so the function takes arguments from elements in the array, not an array

let fn = function () {
    console.log(arguments)
}

fn.apply(null[1.2[3.4]]);
Copy the code

Therefore, one of the things that apply does frequently is to iterate over array elements into function arguments

Example a

Math.max() does not accept arrays, so finding the maximum value of a very long array can be cumbersome

We can use the apply method to pass the array to math.max ()

The essence is to unpack the array of arguments and pass it to math.max ()

let answer = Math.max.apply(null[2.4.3])
console.log(answer) / / 4

// Note the following three equivalents
Math.max.apply(null[2.4.3])
Math.max.call(null.2.4.3)
Math.max(2.4.3)
Copy the code

Example 2: Merge two arrays

It is worth noting that the ARR2 array is broken down into arguments

// Merge the second array into the first array
'celery', 'beetroot');
let arr1 = ['parsnip'.'potato']
let arr2 = ['celery'.'beetroot']

arr1.push.apply(arr1, arr2)
// Attention!! This means to specify that the push method is called
// So when this = arr1
// arr1 calls push
// if ('celery', 'beetroot')

console.log(arr1)
// ['parsnip', 'potato', 'celery', 'beetroot']
Copy the code

Of course, when using Apply, be careful not to bind the pointer to this; otherwise, an error may be reported

Math.max.apply(null[2.4.3]) // It works perfectly
arr1.push.apply(null, arr2) Uncaught TypeError: array.prototype. Push called on null or undefined

/ / that
Math = {
  max: function (values) {
    // This is not used}}Array.prototype.push = function (items) {
  // This -> The array itself that calls push
  // If this is null, push into null and an error will be reported
  // Array.prototype.push called on null or undefined
}

// The following three values are completely equivalent, since this is already arr1
Array.prototype.push.apply(arr1, arr2)
arr1.push.apply(arr1, arr2)
arr2.push.apply(arr1, arr2)
Copy the code

Small holdings test

function xx() {
console.log(this)
}
xx.call('1') // ??
xx() // ??
Copy the code

4.3 the bind

let newfun = fun.bind(thisArg[, arg1[, arg2[, ...]]])
Copy the code

This role

Note that bind has no effect on the original function

  1. Create a new function that is a copy of the original function
  2. Change the this and argument of the new function
  3. Return this new function

4.3.2 Binding function and objective function

The function bind() returns a new function instead of the target function.

I personally prefer to distinguish between new and original functions, because the more new nouns there are, the more difficult it is to understand

So what happens when a new function is called? Remember that the following statement is equivalent to calling/applying the call/apply function and specifying that you pass this

function xx() {
  console.log(this)}let foo = xx.bind({'name':'jason'})
// foo -- new function
// xx -- function

foo()

// When a new function is called, the operation on the original function is as follows (pseudocode)
function foo(){
  xx.call({'name':'jason'})}Copy the code

4.3.3 the bind () parameter

  1. bind(this,arg1,arg2…) willarg1,arg2...Inserted into theThe new functionThe binding functionThe arguments of theThe starting position
  2. Calling a new function, the parameter passed again will only followarg1,arg2...behind
function list() {
    // The target function is:
    return Array.prototype.slice.call(arguments);
}

// new function
let leadingThirtysevenList = list.bind(null.37.38);

let newList1 = leadingThirtysevenList();
let newList2 = leadingThirtysevenList(1.2.3);
let newList3 = leadingThirtysevenList(-1, -2);

console.log(newList1) // [37, 38]
console.log(newList2) // [37, 38, 1, 2, 3]
console.log(newList3) // [37, 38, -1, -2]
Copy the code

4.3.4 Natively implement a bind using this + call/apply

Thinking process

Implementing BIND is all about implementing bind

  1. The first argument to bind is this
  2. Bind can return a new function that calls the original function and can specify its this, as well as accept arguments
  3. The arguments passed by the new function come after those passed by bind

code

Function.prototype._bind = function () {
    // bind specifies this
    let bindThis = arguments[0]
    // The argument passed by bind
    let bindArgs = Array.prototype.slice.call(arguments.1)
    // this refers to the old function that called _bind
    let oldFunction = this

    // Return a new function
    return function () {
        // The new function is the same as the old function, but it only calls the old function with apply and then returns the value of the old function

        // Convert arguments to arrays for the new function
        let newArgs = Array.prototype.slice.call(arguments.0)

        // merge bindArgs and newArgs, and pass newArgs after bindArgs to the old function
        return oldFunction.apply(bindThis, bindArgs.concat(newArgs))
    }
}

/ / test
function fn() {
    console.log(arguments)}let newFn = fn._bind(null.1.2);
newFn(4.6)
Copy the code