[Nodejs internal organs Heart] this comprehensive analysis

Please indicate the source

this

Before understanding the binding process this, we must first understand the call location: Call position is the position function is called (rather than a statement of position) in the code.

Call stack and call location

	function foo() {
		// 当前调用栈是:foo
		// 当前调用位置是全局作用域
		console.log("foo")
		bar() // bar的调用位置
	}
	function bar() {
		// 当前调用栈是 foo -> bar
		// 当前调用位置在foo中
		console.log("bar")
	}

Notice how we analyze the real position of calling a function, because it determines the binding of this

this binding rules

1. default binding

Independent function callWhen using the default bindings, this points to the default binding global object or undefined (depending on whether the strict mode).
When the functionrunIn non-strict mode, the default binding can be bound to the global object. this will be bound to undefined strict mode

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

varible = 2
foo() // 2

The above example this points to a global variable, then how do we know where the application of the default binding it? You can look at the analysis position calling foo () is how the call. In the code,foo () is used directly without any modification of the reference to the calling functionTherefore only use the default bindings, other rules can not be applied.

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

varible = 2
foo() 
// TypeError: Cannot read property 'varible' of undefined
function foo() {
	console.log(this.varible)
}
varible = 2;

(function(){
	'use strict'
	foo()
})() // 2

//这里就说明了只有在函数运行在严格模式下默认绑定才会绑定undefinded, 
//而不是函数的调用位置是严格模式。
2. implicit binding

This rule is mainly on accountCall positionIs there a contextObjects

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

obj = { a: 100, foo: foo }

obj.foo() // 100

When the above code foo () is called, it does point to a foothold object obj. When the function references have context object, the implicit this binding rule will function call is bound to the context object. Because the call foo () when this is bound to obj, and therefore this.a obj.a is the same.

Object attribute refers to only the last one in the chain will affect the binding of this (or recent calls that can affect the function of the object from)

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

obj2 = {
	a: 200,
	foo: foo
}

obj1 = {
	a: 100,
	obj2: obj2
}

obj1.obj2.foo() //200

Implicit loss

One of the most common problem is that this binding is implicit binding function of binding object will be lost, that it will apply the default bindings, so this is bound to put the global object or undefined, depending on whether the strict mode .

function demo() {
	console.log(this.a)
}

const obj = {
	a: 100,
	demo: demo
}

const bar = obj.demo
a = 200
bar() // 200

// 此时的 bar() 其实是一个不带任何修饰的函数调用,
// 因此应用了默认绑定。
function demo() {
	console.log(this.a)
}

const obj = {
	a: 1,
	demo: demo
}

a = "i am global"

// 在浏览器中执行
setTimeout(obj.demo, 100) // i am global

// 在node.js环境中执行
setTimeout(obj.demo, 100) // undefined
// 这是因为node.js 中 _onTimeout = obj.demo 是被timer._onTimeout()调用。
// 所以this为timer对象
3. Show Binding

When implicit binding analysis, we must be contained in an internal object points to a function of the properties and function of the indirect reference by this attribute, this thereby indirectly (implicitly) bound to the object.

call and apply

Their first argument is an object, they will bind to this object, and then specify this when calling this function. Because this can be specified directly bound object, so we call it explicitly binding.

function demo() {
	console.log(this.a)
}
const obj = { a: 1 }

demo.call(obj) // 1

If you pass a primitive value (string type, Boolean type or digital type) as a binding object to this, and the original value will be converted to the form of its objects (that is, new String (...), new Boolean (...) or new Number (...)). This is often referred to as "packing."

call and apply difference:

They differ only difference parameters.

  1. Apply the second parameter is an array, a parameter list structure will be passed to the method. Function.apply (obj, args)
  2. call can be passed an argument list Function.call (obj, [params1 [, params2]])
Hardwire

es5 provides a built-in methods Function.prototype.bind, bind (...) function returns a new hard-coded, it will set the parameter to this context and calls the original function.

function demo(str) {
	console.log(this.a, str)
}

const obj = { a: 2 }

const bar = demo.bind(obj);

bar("heihei"); // 2 heihei

forEach second argument can specify this context (context)

function demo(el) {
	console.log(el, this.id);
}
const obj = { id: 'test!!' };

[1,2,3].forEach(demo, obj)

// 1 'test!!'
// 2 'test!!'
// 3 'test!! 

The function is actually through call (...) or apply (...) to achieve the explicit binding

4. new bindings

In traditional class-oriented language, the "constructor" is a special class method, calls the constructor of the class is initialized when using the new class. Usually in the form like this:

     something = new MyClass(..);

JavaScript 也有一个 new 操作符,使用方法看起来也和那些面向类的语言一样,绝大多数开发者都认为 JavaScript 中 new 的机制也和那些语言一样。然而,JavaScript 中 new 的机制实际上和面向类的语言完全不同。

In JavaScript, are just a few constructor function is called when the new operator. They do not belong to a class, it will not instantiate a class. In fact, they can not even be said to be a special function type, they are just ordinary function to be called the new operator only.

Use new to call a function, or event of constructor calls, automatically performs the following operations. (This part is more important, we have to focus on learning about)

  1. Create or construct a new object
  2. This object will be executed [[prototype]] connection
  3. This new object will be bound to the function of this
  4. If the function does not return other objects, the function call will automatically return to new expressions of this new object
function demo(a) {
 this.a = a
}

const obj = new demo(2)
console.log(obj) // demo { a: 2 }

// 使用 new 来调用 demo(..) 时,我们会构造一个新对象并把它绑定到 demo(..) 调用中的 this 上。

new is this last method may affect the binding behavior of the function call, we call the new binding.

priority

These four priority rules

1. Display and implicit

function demo() {
	console.log(this.a)
}

const obj1 = { a: 1, demo: demo }
const obj2 = { a: 2, demo: demo }

obj1.demo() // 1
obj2.demo() // 2

obj1.demo.call(obj2) // 2
obj2.demo.call(obj1) // 1

可以看到显示绑定的优先级更高

  1. new and implicit bindings
function demo(str) {
	this.a = str
}

const obj1 = { demo: demo }

obj1.demo(2);
console.log(obj1.a) // 2

obj1.demo.call(obj2={}, 3)
console.log(obj2.a) // 3

const obj3 = new obj1.demo(4)
console.log(obj1.a) // 2
console.log(obj3.a) // 4

const obj5 = obj1.demo.bind(obj4=Object.create(null))
obj5(5)
console.log(obj4.a) //5

const obj6 = new obj5(6)
console.log(obj4.a) // 5
console.log(obj6.a) // 6

可以看到new绑定的优先级比隐式绑定和bind硬绑定都要高, 这是因为es5的bind方法的实现会判断硬绑定函数是否是被 new 调用,如果是的话就会使用新创建 的 this 替换硬绑定的 this。(new绑定是否优先级高这是根据bind的实现决定的)

Why use it in a new hard-binding function in? Directly using an ordinary function is not more simple?

The reason for using the new hard-binding function, the main purpose is to pre-set some of the parameters of the function, so when using the new initialization can be passed only remaining parameters. One function of the bind (...) is in addition to other parameters can be the first parameter (first parameter is used to bind this) are passed to the function of the lower layer (this technique is called "part of the application", is "currying" a)

function demo(arg1, arg2) {
	this.a = arg1 + arg2
}

const bar = demo.bind(null, "debug: ")
const foo = new bar("i am jim")

console.log(foo.a) // debug: i am jim

Summarize the contents of the above

this binding order of priority

  1. Whether the function call (new binding) in new if it is, then this is bound to the newly created object? (Ex: const bar = new foo ())
  2. Whether by function call, apply (explicit binding) or a hard-bound call? If so, this is bound to the specified object. (Ex: bar = foo.call (obj2))
  3. Whether the function call (implicit binding) in the context of an object? If so, this is bound to the context object. (Ex: bar = obj.foo ())
  4. If not, then use the default bindings. If strict mode, it is bound to undefined, otherwise bound to the global object (window or global). (Ex: var bar = foo ())

For normal function call, understanding this knowledge you can understand this is a binding principle. However, there are some exceptional cases

1. If you take this as a null or undefined binding target incoming call, apply or bind, these values ​​are ignored when you call, the practical application of the default binding rules (ie bound to the global object (window or global))
// 运行在非严格模式下,如果在严格模式下会报错,因为严格模式this是默认绑定undefined
function demo() {
	this.a = 100
}

demo.call(null)
// run in node.js
console.log(global.a) // 100

// run in browser
console.log(window.a) // 100	

Use null and undefined as the this binding

A very common practice to use apply (...) to "expand" an array and passed as an argument to a function. Similarly, the bind (...) can be curried (pre-set parameters) parameters, this method can be useful,

function test(a, b) {
	console.log("a:" + a + ", b:" + b)
}

// 把数组展开成参数
test.apply(null, [2,3])
// 在 ES6 中,可以用 ... 操作符代替 apply(..) 来“展
// 开”数组, foo(...[1,2]) 和 foo(1,2) 是一样的,这样可以避免不必要的
// this 绑定。可惜,在 ES6 中没有柯里化的相关语法,因此还是需要使用
// bind(..) 。

// 使用bind来进行柯里化
const curring = test.bind(null, 2)
curring(5) // a:2, b:5

However, always use null to ignore this binding might produce some side effects. If a function do use this (such as a third-party library function), the default binding rules that will bind to this global object (the object in the browser is a window), which will lead to unpredictable consequences (such as modifying the global object). Obviously, this approach may lead to many difficult to analyze and track the bug.

Safer this

A kind of "safer" approach is to pass a special object, this is bound to put this object will not have any side effects on your program. We can create a "the DMZ" (Demilitarized
Zone, DMZ) object, which is an empty object.
If we always pass a DMZ object while ignoring this binding, then do not worry about anything, because any object in the air, will not have any impact on the global object for this use will be restricted.

Create an empty object easiest way in JavaScript are Object.create (null)
Object.create (null) {} and the like, but does not create Object.prototype this commission, so it} "is more empty than { "

function test(a,b) {
	console.log( "a:" + a + ", b:" + b );
}
// 我们的 DMZ 空对象
var ø = Object.create( null );

// 把数组展开成参数
test.apply( ø, [2, 3] ); // a:2, b:3

// 使用 bind(..) 进行柯里化
var bar = test.bind( ø, 2 );
bar( 3 ); // a:2, b:3
Indirect reference

Another thing to note is that you may (intentionally or unintentionally) to create a function of "indirect references" in this case, calling this function will apply the default binding rules.

function test() {
	console.log(this.a)
}

var a = 1
var foo = { a: 3, test }
var bar = { a: 4 }
foo.test() // 3
(bar.test = foo.test)(); // 1

Assignment expression p.foo = o.foo return value is a reference to the objective function, so the position is to call foo () instead p.foo () or o.foo (). According to we have said before, this will apply the default bindings.

note:

For default binding, the decision is not binding object calls this position is in strict mode, but the body of the function 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.

this morphology (arrow for the function () => {})

Before we introduce the four rules already contain all the normal functions. But ES6 introduces a type of special functions can not use these rules: arrow function.
Arrow key functions are not defined using the function, but the use is called "fat arrow" operator => defined.
Arrow four standard rules function does not use this, but this is determined according to an outer layer (function or global) scope.

function test() {
	return () => {
		// this 继承自test
		console.log(this.a)
	}
}

const obj1 = { a: 1 }
const obj2 = { a: 2 }

const foo = test.call(obj1)
foo.call(obj2) // 1 
//并不是返回 2

foo () foo () of this function internally created when the arrow will catch calls. Since foo () of this is bound to obj1, bar (arrow referenced function) of this will be bound to obj1, the binding function of the arrow can not be modified.(New does not work!)

Arrow callback function is most commonly used in, for example, or a timer event handler:

function foo() {
  setTimeout(() => {
    // 这里的 this 在此法上继承自 foo()
    console.log( this.a );
  },100);
}

var obj = {
  a:2
};

foo.call( obj ); // 2

If you often write this style of code, but most of the time will use the arrows or self = this function to deny this mechanism, then you probably should:

  1. Use only lexical scoping and completely abandoned this style of error codes;
  2. Full use of this style, the bind used when necessary (...), to avoid the use of self = this function and arrows.

Of course, the program includes two styles of code that can be run properly, but generally makes the code harder to maintain the same function or a program using the same mix these two styles, and may also be more difficult to write.

summary

To determine if this is running a binding function, we need to find a position directly call this function. After finding can be applied in order to determine the following four rules that bind the target of this.

  1. A new call? Bound to the newly created object.
  2. Called by the call or apply (or bind)? Bound to the specified object.
  3. Called by the context object? Bound to the context object.
  4. Default: Bind to undefined in strict mode, otherwise it is bound to the global object.

Be sure to note that some calls may inadvertently use the default binding rules. If you want to "safer" to ignore this binding, you can use a DMZ objects, such as ø = Object.create (null), to protect the global object.

ES6 arrow function does not use four criteria binding rules, but to decide this based on the current lexical scope, specifically, the function will inherit this arrow outer function call bindings (whether this is bound to what). This fact, and before ES6 self = this mechanism in the code as

Layout may be a bit chaotic, but also need a lot of improvement, Tell me who bear with me, ha ha!

Please indicate the source yo : https: //blog.csdn.net/a675697174/article/details/103689501

Published 35 original articles · won praise 2 · views 20000 +

Guess you like

Origin blog.csdn.net/a675697174/article/details/103689501