This is the 7th day of my participation in the August Text Challenge.More challenges in August

Today we continue with JavaScript you Don’t Know to explore the this keyword in JavaScript

We talked about this earlier when we looked at scopes

This is bound at run time, not at write time, and its context depends on various conditions at the time the function is called. The binding of this has nothing to do with the position of the function declaration, except how the function is called.

The this keyword is not only found in JavaScript, but also in many languages such as Java. This is a special keyword that is automatically defined in the scope of all functions. The ambiguity of this makes it a headache. Let’s first understand why this is used.

1. Whythis

So let’s say we have two objects that represent two people

let me = {
  name: "yk"};let you = {
  name: "YK bacteria"};Copy the code

We’re going to define a function that introduces itself, and without this, we’re going to introduce ourselves differently by passing in different arguments

function speak(context) {
  console.log('Hello, I am${context.name}`);
}
Copy the code

We call the function like this to pass in arguments

speak(me); // Hello, I am YK
speak(you); // Hello, I am YK bacteria
Copy the code

If we use this, the function can be defined like this

function speak() {
  console.log('Hello, I amThe ${this.name}`);
}
Copy the code

Call the function this way

speak.call(me); // Hello, I am YK
speak.call(you); // Hello, I am YK bacteria
Copy the code

So, this provides a more elegant way to implicitly pass an object reference, so the API can be designed to be more == concise == = and easy to == reuse == =.

About 2.thisThe misunderstanding of

2.1 thisIt’s not pointing to the function itself

Use this in a function. In English grammar, this always refers to itself, whereas in a function this does not refer to the function itself.

If you want to refer to a function object itself from within, this is not enough. Generally you need to refer to a function object by a lexical identifier (variable) that refers to it.

function foo() {
	foo.count = 1;
}
Copy the code

2.2 thisDoes not point to the lexical scope of a function

Also, as I mentioned in my last post, JavaScript code execution follows lexical scope, but this does not in any case refer to the lexical scope of a function.

When a function is called, a == execution context == is created. This execution context contains information about where the function is called (call stack), how the function is called, the parameters passed in, and so on. This is an attribute of the context that is used during function execution.

So this is actually a binding that happens when the function is called ==, and what it points to depends entirely on where the function is called.

3. What is call stack and call location

== call location == is where the function is called in code (not declared).

This sentence seems so simple, it even feels like nonsense. But in fact, in some programming modes, the actual call location is hidden, making it hard to tell where the call location really is, right

The most important thing is to analyze the == call stack == (that is, all the functions called to reach the current execution location). The call location we care about is in the previous call to the currently executing function.

function fun1() {
  // Current call stack: fun1
  // Current calling location: global scope
  console.log("fun1");
  fun2(); // call fun2 at fun1
}

function fun2() {
  // The stack is fun1 -> fun2
  // Current call location: fun1
  console.log("fun2");
  fun3();
}

function fun3() {
  Fun1 -> fun2 -> fun3
  // Current call location: fun2
  console.log("fun3");
}

fun1(); // where fun1 is called: global scope
Copy the code

Make a breakpoint on the first line of fun3, and the debug tool will see the current call stack and the value of this

4. thisBinding rules of

Now that we know the call stack, we need to know how the call location determines the binding object for this during the execution of the function

Once the call location is found, the binding of this is determined according to the following four binding rules

4.1 Default Bindingfun()

Function call type: Independent function call Default binding: The default rule when no other rule can be applied

This is called with the == default binding ==, so this refers to a global object (node is the global object, browser is the window object).

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

foo(); 
// Window {Window: Window, self: Window, document: document, name: "", location: location,... }
/ / 2
Copy the code

If strict mode is used, you cannot use a global object for the default binding, so this is bound to undefined:

function foo() {
  'use strict'
  console.log(this);
}

foo(); // undefined
Copy the code

For the default binding, what determines the this binding object is not whether the call location is in strict mode, but whether the function body is in strict mode. If the function body is in strict mode, this will be bound to undefined, otherwise this will be bound to the global object.

4.2 Implicit Bindingobj.fun()

When a function refers to a context object (whether the function is owned or contained by an object), the implicit binding rule binds this in the function call to that context object

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

var obj = {
  a: 2.foo: foo,
};

obj.foo(); 
Copy the code

Whether you define foo directly in obj or define foo first and then add it as a reference property, the function is not strictly an obj object

The call location references the function using the OBJ context, so you can say that the obJ object “owns” or “contains” the function when it is called

Only the previous or last level in the object attribute reference chain is relevant at the call location.

function foo() {
  console.log(this); // {a:2, foo:f}
  console.log(this.a); / / 2
}

var obj = {
  a: 2.foo: foo,
};

var yk = {
  a: 222.obj: obj,
};

yk.obj.foo();
Copy the code

Cases where implicit binding is lost

This implicit binding is sometimes lost, let’s look at the following case

① Function alias

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

var obj = {
  a: "Local parameter".fun1: fun1,
};

var fun2 = obj.fun1; // Function alias

var a = "Global parameter";

obj.fun1(); // Local parameters
fun2(); // This is a global parameter.
Copy the code

Fun2 is a reference to obj.fun1, but in fact it refers to the fun1 function == itself ==, so the following call to fun2() is actually an undecorated function call, so the default binding is applied.

② Parameter transfer function

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

function fun2(fn) {
  // obj.fun1 passes in the reference value, which is actually fun1
  fn(); // Call directly, pointing to window
}

var obj = {
  a: "Local parameter".fun1: fun1,
};

var a = "Global parameter";

fun2(obj.fun1); // Global parameters
Copy the code

Argument passing is essentially an implicit assignment, so when we pass in a function we get an implicit assignment, so the result is the same as in the previous example.

This explains why it’s so common to lose this in callback functions when using the built-in setTimeout() function in JavaScript, right

setTimeout(obj.fun1, 100) // 'global object'
Copy the code

This is how the callback function is called inside setTimeout

function setTimeout(fn, delay) {
	// Wait for dealy milliseconds
	fn(); // obj. Fun1 is the reference value, and fun1 is called directly, pointing to window.
}
Copy the code

③ Event handler

There is also a case where this behaves unexpectedly: the function that calls the callback might modify this. Event handlers in some popular JavaScript libraries often force the this of the callback function to be bound to the DOM element that triggered the event.

In either case, the change to this is unexpected, and you actually have no control over how the callback function is executed, so there is no way to control where the call is called to get the desired binding.

4. 3 Explicit Bindingfun.call(obj)

The three methods apply, call, and bind on Function’s prototype object explicitly change the direction of this

【JS】 Function definition and call method – this function refers to the problem -call-apply-bind method use and customization

First take a look atapplycallExplicitly bound

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

var obj = {
  a: "Local object"}; fun1.call(obj);// Local objects
Copy the code

Explicit binding still doesn’t solve the missing binding problem we mentioned earlier

This is because an explicit binding executes the function immediately, and the execution time of the function in the callback function is uncertain, so we need to bind this to the specified object in advance and call the callback function explicitly when needed.

Explicitly forced binding (hard binding) solves this problem

(1) Explicit mandatory binding – hard bindingbind

Fun2 () is created and fun1.call(obj) is manually called inside it, so == forces == to bind fun1’s this to obj. No matter how fun2 is later called, it will always manually call fun1 on obj.

This binding is explicitly forced and is therefore called hard binding

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

var obj = {
  a: "Local object"};function fun2() {
  fun1.call(obj); // Explicit binding
}

fun2(); // local object [internal binding]

setTimeout(fun2, 100); // Local objects

// Hardbound fun2 cannot change this
fun2.call(window); // Local objects
Copy the code

(2) Hard binding application scenarios

Create a wrapper function that accepts parameters and returns values

function fun1(something) {
  console.log(this.a, something);
  return this.a + something;
}

var obj = {
  a: 2};// Create a wrapper function that accepts parameters and returns values
function fun2() {
  return fun1.apply(obj, arguments);
}

var b = fun2(3); / / 2, 3

console.log(b); / / 5
Copy the code

② Create a secondary binding function that can be used repeatedly

function fun1(something) {
  console.log(this.a, something);
  return this.a + something;
}

// Auxiliary binding function
function bind(fn, obj) {
  return function () {
    return fn.apply(obj, arguments);
  };
}

var obj = {
  a: 2};var fun2 = bind(fun1, obj);

var b = fun2(3); / / 2, 3
console.log(b); / / 5
Copy the code

In fact, the auxiliary binding function that JavaScript has already created for us is the bind() method on the function prototype

function fun1(something) {
  console.log(this.a, something);
  return this.a + something;
}

var obj = {
  a: 2};// Use bind
var fun2 = fun1.bind(obj);

var b = fun2(3); / / 2, 3
console.log(b); / / 5
Copy the code

③ Context of API call

Many functions in third-party libraries, as well as many new built-in functions in the JavaScript language and host environments, provide an optional argument, often called a “context,” which serves the same purpose as bind(..) Also, make sure your callback uses the specified this

function fun(el) {
	console.log(el, this.id);
}
var obj = {
	id: 'yk'
}
// The second argument specifies this
[1.2.3].forEach(fun, obj); // 1 yk 2 yk 3 yk
Copy the code

These apis implement explicit binding internally

4.4 newThe binding

The mechanics of new in JavaScript are completely different from class-oriented languages

In JavaScript, constructors are simply functions that are called when the new operator is used. They do not belong to a class, nor do they instantiate a class. In fact, they are not even a special type of function; they are just ordinary functions called by the new operator

All functions in JavaScript can be called with new, called constructor calls

When a function is called with new, or when a constructor call occurs, the following action is automatically performed:

  1. Create (or construct) a brand new object
  2. This new object will be joined by [[Prototype]].
  3. This new object will be bound to the function callthis
  4. If the function returns no other object, the function call in the new expression automatically returns the new object
function fun(a){
	this.a = a;
}
// Bind this in the fun constructor to obj
var obj = new fun(2)
console.log(obj.a) / / 2
Copy the code

The custom of new

/** * create an instance of the Fn constructor *@param {Function} Fn
 * @param  {... any} args
 * @returns* /
export default function newInstance(Fn, ... args) {
  // 1. Create an object
  // Create an empty object instance object for the Fn instance object
  const obj = {};
  // Modify the prototype object of the new object
  // Assign Fn's prototype property to obj's __proto__ property
  obj.__proto__ = Fn.prototype;
  // 2. Modify this inside the function to point to a new object and execute
  //
  constresult = Fn.call(obj, ... args);// 3. Return a new object
  // return obj
  If the constructor has a value, object A is returned; otherwise, an instance object is returned
  return result instanceof Object ? result : obj;
}
Copy the code

We know to whom this is bound as long as we find where the function is called == and which == rule we use

If more than one binding rule is satisfied, it depends on the priority between them

4.5 Binding Priority

New Binding > Explicit Binding > Implicit Binding > Default binding (minimum)

① Explicit binding > Implicit binding

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

let obj1 = {
  a: "A in obj1".fun: fun,
};

let obj2 = {
  a: "A in Obj2".fun: fun,
};

obj1.fun(); // implicitly bind obj1
fun.call(obj2); // Explicitly bind obj2
// Compare priorities
obj1.fun.call(obj2); // obj2
Copy the code

② New binding > Implicit binding

function fun(a) {
  this.a = a;
}

let obj1 = {
  fun: fun,
};

obj1.fun("Implicit binding");
console.log(obj1.a); // "implicit binding"

let obj2 = new fun("The new binding");
console.log(obj2.a); // "new bind"

// Compare priorities
let obj3 = new obj1.fun("The new binding");
console.log(obj1.a); // "implicit binding"
console.log(obj3.a); // "new bind"
Copy the code

③ New binding > Explicit binding

function fun(a) {
  this.a = a;
}

let obj1 = {};

let fun1 = fun.bind(obj1);
fun1("Hard-bound A");
console.log(obj1.a); // hardbound a

let fun2 = new fun1("New bound a");
console.log(obj1.a); // hardbound a
console.log(fun2.a); // new bind a
Copy the code

4.6 Rule Summary

① Called by new? Bind to the newly created object. ② By call or apply (or bind)? Binds to the specified object. ③ Called by context objects? Bind to that context object. ④ Default: bind to undefined in strict mode, otherwise bind to global object.

5. Binding exceptions

5.1 Passed in for explicit bindingnull

If you pass null or undefined as a binding object for this to call, apply, or bind, these values will be ignored during the call and the default binding rule uses will be applied

function fun(a, b) {
  console.log(`a:${a}, b:${b}`);
}

// Expand the array as an argument
fun.apply(null[2.3]); // a:2, b:3

// The function is currified
let fun1 = fun.bind(null.2);
fun1(3); // a:2, b:3
Copy the code

Always passing null is not good either. You can pass an empty object øøø

function fun(a, b) {
  console.log(`a:${a}, b:${b}`);
}

let ø = Object.create(null);

// Expand the array as an argumentFun. Apply (ø, [2.3]); // a:2, b:3

// The function is currified
letFun1 = fun. Bind (ø,2);
fun1(3); // a:2, b:3
Copy the code

Using variable names ø not only makes functions more “safe”, but also improves code readability as ø means “I want this to be null”, which is clearer than null.

5.2 Indirect Reference

It is possible (intentionally or unintentionally) to create an “indirect reference” to a function, in which case the default binding rules apply to calling the function

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

var a = "Global A";

let obj1 = {
  a: "A" in the obj1.fun: fun
};
let obj2 = {
  a: "A" in the obj2
};

obj1.fun(); // a in obj1

obj2.fun = obj1.fun;
obj2.fun(); // a in obj2

(obj2.fun = obj1.fun)(); // global a
Copy the code

The reference to the target function when the assignment expression returns the value, so the call location is fun(), not obj2.fun() or obj1.fun().

5.3 soft binding

Hard binding can greatly reduce the flexibility of functions, making it impossible to modify this using either implicit or explicit binding.

You can specify a global object and a value other than undefined for the default binding to achieve the same effect as hard binding, while retaining the ability to modify this implicitly or explicitly.

Function.prototype.softBind = function (obj) {
  let fn = this;
  // Capture all parameters
  let curried = [].slice.call(arguments.1);
  let bound = function () {
    return fn.apply(
      // If this is bound to a global object or undefined, the specified default object obj is bound to this, otherwise this is not modified
      !this || this= = = (window || global)? obj :this,
      curried.concat.apply(curried, arguments)); }; bound.prototype =Object.create(fn.prototype);
  return bound;
};


function fun() {
  console.log(`name:The ${this.name}`)}let obj1 = {
  name: 'obj1'
}
let obj2 = {
  name: 'obj2'
}
let obj3 = {
  name: 'obj3'
}

let funObj = fun.softBind(obj1)
funObj() // name:obj1

obj2.fun = fun.softBind(obj1)
obj2.fun() // name:obj2

funObj.call(obj3) // name:obj3

setTimeout(obj2.fun, 10) // name:obj1
Copy the code

5.4 Arrow Function

Arrow functions are not defined using the function keyword

Arrow functions do not use the four standard rules for this, but instead determine this based on the outer (function or global) scope.

The binding of the arrow function cannot be modified. (Neither does New!)