this point resolution


1. Call location

Before understanding the binding process of this, we should first understand the call location: the call location is the location where the function is called in the code (not the declared location). Only by understanding the call location can we understand that this refers to the declaration.

function baz() {
	// Current call stack
	// The current call location is the global scope.
	console.log('baz')
	bar() // Call location of bar
}

function bar() {
	// The current call stack is Baz > bar.
	// The current call location is in baz.
	console.log('bar')
	foo() // Call location of foo
}
function foo() {
	// The current call stack is Baz > bar > foo.
	// The current call location is in bar.
	console.log('foo')
}
baz() // Call location of baz

2. Binding rules

1. Default binding

  • The most commonly used function call type: independent function call, which can also be regarded as the default rule when other rules cannot be applied
function foo() {
	console.log(this.a)
}
var a = 2
foo() // 2

When foo() is called, this.a is resolved to the global variable a. because the default binding is applied when the function is called, this points to the global variable.

Strict mode

If strict mode is used, the global object cannot be used for the default binding, so this is undefined:

"use strict"

function foo() {
	console.log(this.a)
}
var a = 2
foo() // TypeError: this is undefined;

Note: Although the binding of this depends on the calling location, the default binding can only be bound to the global object if foo() runs in non strict mode; However, calling in strict mode does not affect the default binding

function foo() {
	// Non strict mode
	console.log(this.a)
}
var a = 2
(function() {
	"use strict"
	foo()
})() // 2. The default binding is not affected
,

2. Implicit binding

The second is to consider whether the call location is also a context object, or whether it is owned or contained by an object

function foo() {
	console.log(this.a)
}
var obj = {
	a: 2,
	foo: foo
}
obj.foo() // 2

The call location uses the obj context to reference the function. It can be said that the obj object owns or contains the object reference when the function is called. When foo () is called, it is preceded by a reference to obj. When the function references a context object, the implicit binding rule will bind this in the function call to this context object, because this is bound to obj when foo() is called, so this.a is the same as obj.a.
Only the upper or last layer in the object attribute reference chain works at the call location

function foo() {
	console.log(this.a)
}
var obj2 = {
	a: 10,
	foo: foo
}
var obj1 = {
	a: 2,
	obj1: obj2
}
obj1.obj1.foo()

Implicit loss

The most common problem is that the implicitly bound function will lose the bound object, that is, it will apply the default binding to bind this to the global object or undefined.

function foo() {
	console.log(this.a)
}
var obj = {
	a: 2,
	foo : foo
}
var bar = obj.foo()
var a = 'global'
bar() // global

Although bar is a reference to obj.foo, it refers to the foo function itself. Therefore, bar() at this time is actually a function call without any modification and applies the default binding.

Another way is to pass in the callback function:

function foo() {
	console.log(this.a)
}
function foo1(fn) {
	fn()
}
var obj = {
	a: 2,
	foo: foo
}
var a = 'golbal'
foo(obj.foo) // folbal

Parameter passing is an implicit assignment, so it will also be implicitly assigned when passing in a function

3. Display binding

function foo() {
	console.log(this.a)
}
var obj = {
	a: 2
}
foo.call(obj) // 2

Through call, its this can be forcibly bound to obj when calling foo. If an original value is passed in as the object bound by this, the original value will be transformed into its object form (wow String(), etc.). It is often called "boxing", but displaying bindings does not solve the problem of missing bindings.

Hard binding

function foo() {
	console.log(this.a)
}
var obj = {
	a: 2
}
var bar = functon() {
	foo.call(obj)
}
bar() // 2
setTimeout(bar, 1000) // 2
// A hard bound bar can no longer modify its this
bar.call(window) // 2

The function bar() is created and foo.call(obj) is manually called inside it, so this of foo is forcibly bound to obj. No matter how the bar function is called later, it always calls foo on obj manually. This binding is an explicit mandatory binding called hard binding.

The typical application scenario of hard binding is to create a package function to receive the return value of parameters:

function foo(something) {
	console.log(this.a, something)
	return this.a + something
}
var obj = {
	a: 2
}
var bar = function() {
	return foo.apply(obj, arguments)
}
var b = bar(3) // 2, 3
console.log(b) // 5

Another method is to create a reusable assignment function:

function foo(something) {
	console.log(this.a, something)
	return this.a + something
}
// Simple auxiliary binding function
function bind(fn, obj) {
	return function() {
		return fn.apply(obj, arguments)
	}
}
var obj = {
	a: 2
}
var bar = bind(foo.obj)
var b = bar(3) // 2, 3
console.log(b) // 5

Context of API call

Many functions of third-party libraries, as well as many new built-in functions in js language and host environment, provide an optional parameter, commonly known as "context", which is the same as bind(...) to ensure that the callback function points to this.

function() {
	console.log(el, this.id)
}
var obj = {
	id: 'aaa'
}
// Bind this to obj when calling foo
[1, 2, 3].forEach(foo, obj) // 1 aaa   2 aaa    3 aaa

4. new binding

When using new to call a function, or when a constructor call occurs, the following operations will be performed automatically:

  1. Create (construct) a completely new object
  2. The new object will be connected by [[Prototype]]
  3. The new object is bound to this of the function call
  4. If the function does not return another object, the cold call in the new expression will automatically return the new object‘
function foo(a) {
	this.a = a
}
var bar = new foo(2)
console.log(bar.a) // 2

When using new to call foo, a new object will be constructed and bound to this in foo(...) call. New is the last method that can affect this binding behavior during function call, which is called new binding.

5. this in the arrow function points to

The arrow function does not apply to the four rules of this, but determines the scope of this according to the outer (function or global) scope.

function foo() {
	// Returns an arrow function
	return (a) => {
		// this inherits from foo()
		console.log(this.a)
	]
}
var obj1 = {
	a: 2
}
var obj2 = {
	a: 3
}
var bar = foo.call(obj1)
bar.call(obj2) // 2

The arrow function created inside foo will capture this calling foo(). Since this of foo() is bound to obj1 and this of bar (reference arrow function) is bound to obj1, the binding of arrow function cannot be modified. (new is not allowed)

Arrow functions are most commonly used in callback functions, such as event handlers or timers

function foo() {
	setTimeout(() => {
		console.log(this.a)
	}, 1000)
}
var obj = {
	a: 2
}
foo.call(obj) // 2

3. Determine the priority of this

  1. If the function is called in new, if so, this is bound to the newly created object.
  2. The function is called through call, apply or hard binding. this binds the specified object
  3. The function is called in the context object, and is bound to the context object.
  4. The last is the default binding. If in strict mode, bind to undefined, otherwise bind to global objects

Tags: Javascript Front-end ECMAScript

Posted on Mon, 08 Nov 2021 13:21:04 -0500 by Rincewind