Home  >  Article  >  Web Front-end  >  Detailed explanation of this binding in JavaScript

Detailed explanation of this binding in JavaScript

高洛峰
高洛峰Original
2016-10-13 09:35:021063browse

This can be said to be the most intriguing feature in JavaScript. Just like the various tenses in high school English, such as passive tense, past tense, present tense, and past continuous tense, no matter how many times you miss it, you may still miss it next time. wrong. This article is inspired by "JavaScript You Don't Know Volume 1" and provides a summary of this in javascript.

The first step to learn this is to understand that this neither points to the function itself nor the scope of the function. this is actually the binding that occurs when the function is called, and where it points depends entirely on where the function is called.

Default binding

In javascript, the most commonly used type of function call is independent function call, so this rule can be regarded as the default rule when other rules cannot be applied. If the function is called without any modification, that is, it is called "barely", then the default binding rules will be applied, and the default binding points to the global scope.

function sayLocation() { 
    console.log(this.atWhere) 
} 
 
var atWhere = "I am in global" 
 
sayLocation() // 默认绑定,this绑定在全局对象,输出 “I am in global”

Look at another example

var name = "global" 
function person() { 
    console.log(this.name) //  (1) "global" 
      person.name = 'inside' 
    function sayName() { 
        console.log(this.name) // (2) "global"  不是 "inside" 
    } 
    sayName() // 在person函数内部执行sayName函数,this指向的同样是全局的对象 
} 
person()

In this example, the person function is called in the global scope, so this in sentence (1) is bound to the global object (window in the browser , which is global in node), so the natural output of sentence (1) is the name attribute of a global object, which is of course "global". The sayName function is called within the person function. Even so, this in sentence (2) still refers to the global object, even if the person function sets the name attribute.

This is the default binding rule, which is the most common function calling pattern in JavaScript. The binding rule of this is also the simplest of the four binding rules, which is to bind it to the global scope.

Strict mode in default binding

In javascript, if strict mode is used, this cannot be bound to the global object. Still taking the first example, but this time adding a strict mode declaration

'use strict' 
function sayLocation() { 
    console.log(this.atWhere) 
} 
var atWhere = "I am in global" 
sayLocation() 
// Uncaught TypeError: Cannot read property 'atWhere' of undefined

It can be seen that in strict mode, when this is bound to the global object, it is actually bound to undefined, so the above The code will report an error.

Implicit binding

When a function is called, if the function has a so-called "foothold", that is, a context object, the implicit binding rules will bind this in the function to the context object. If you feel that the above paragraph is not straightforward enough, let’s take a look at the code.

function say() { 
    console.log(this.name) 
} 
var obj1 = { 
    name: "zxt", 
    say: say 
} 
 
var obj2 = { 
    name: "zxt1", 
    say: say 
} 
obj1.say() // zxt 
obj2.say() // zxt

It’s very simple, isn’t it? In the above code, obj1 and obj2 are the starting points of the so-called say function. A more professional term is the context object. When the context object is assigned to the function, this inside the function naturally points to the context object. This is also a very common function calling pattern.

Context is lost during implicit binding

function say() { 
    console.log(this.name) 
} 
var name = "global" 
var obj = { 
    name: "inside", 
    say: say 
} 
var alias = obj.say // 设置一个简写   (1)  
alias() // 函数调用 输出"global"  (2)

You can see that the output here is "global". Why is it different from the above example? We obviously just changed the name of obj.say?

First we do Look at the code in sentence (1) above. Since functions are objects in JavaScript, objects are passed by reference instead of by value. Therefore, the code in sentence (1) is just alias = obj.say = say, that is, alias = say. obj.say only serves as a bridge. alias ultimately refers to the address of the say function and has nothing to do with the object obj. . This is called "losing context". Finally, the alias function is executed, but the say function is simply executed and "global" is output.

Explicit binding

Explicit binding, as the name suggests, explicitly binds this to a context. In JavaScript, three explicit binding methods are provided, apply, call, and bind. The usage of apply and call are basically similar. The difference between them is:

apply(obj,[arg1,arg2,arg3,...] The parameters of the called function are given in the form of an array

call(obj,arg1 ,arg2,arg3,...) The parameters of the called function are given in sequence

After the bind function is executed, a new function is returned.

// 不带参数 
function speak() { 
    console.log(this.name) 
} 
 
var name = "global" 
var obj1 = { 
    name: 'obj1' 
} 
var obj2 = { 
    name: 'obj2' 
} 
 
speak() // global 等价于speak.call(window) 
speak.call(window) 
 
speak.call(obj1) // obj1 
speak.call(obj2) // obj2

So we can see the role of apply, call. It is to bind an execution context to the function, and it is explicitly bound. Therefore, this within the function is naturally bound to the object called by call or apply. The above example illustrates apply and call. Differences in usage.

The bind function returns a new function bound to the specified execution context. Let’s take the above code as an example

// 带参数 
function count(num1, num2) { 
    console.log(this.a * num1 + num2) 
} 
 
var obj1 = { 
    a: 2 
} 
var obj2 = { 
    a: 3 
} 
 
count.call(obj1, 1, 2) // 4 
count.apply(obj1, [1, 2]) // 4 
 
count.call(obj2, 1, 2) // 5 
count.apply(obj2, [1, 2]) // 5

So, the bind method just returns a new function, this function. The this inside specifies the execution context, and the new function returned can accept parameters.

new binding

The last this binding rule to talk about refers to the this binding that occurs when the constructor is called through the new operator. . The first thing to make clear is that there is no concept of a class in JavaScript like in other languages. The constructor is just an ordinary function, but the function name of the constructor starts with a capital letter, and it can be passed through new. It’s just an operator call.

function Person(name,age) { 
    this.name = name 
    this.age = age 
    console.log("我也只不过是个普通函数") 
} 
Person("zxt",22) // "我也只不过是个普通函数" 
console.log(name) // "zxt" 
console.log(age) // 22 
 
var zxt = new Person("zxt",22) // "我也只不过是个普通函数" 
console.log(zxt.name) // "zxt" 
console.log(zxt.age) // 22

上面这个例子中,首先定义了一个 Person 函数,既可以普通调用,也可以以构造函数的形式的调用。当普通调用时,则按照正常的函数执行,输出一个字符串。 如果是通过一个new操作符,则构造了一个新的对象。那么,接下来我们再看看两种调用方式, this 分别绑定在了何处首先普通调用时,前面已经介绍过,此时应用默认绑定规则,this绑定在了全局对象上,此时全局对象上会分别增加name 和 age 两个属性。当通过new操作符调用时,函数会返回一个对象,从输出结果上来看 this 对象绑定在了这个返回的对象上。

因此,所谓的new绑定是指通过new操作符来调用函数时,会产生一个新对象,并且会把构造函数内的this绑定到这个对象上。

事实上,在javascript中,使用new来调用函数,会自动执行下面的操作。

1.创建一个全新的对象

2.这个新对象会被执行原型连接

3.这个新对象会绑定到函数调用的this

4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

四种绑定的优先级

上面讲述了javascript中四种this绑定规则,这四种绑定规则基本上涵盖了所有函数调用情况。但是如果同时应用了这四种规则中的两种甚至更多,又该是怎么样的一个情况,或者说这四种绑定的优先级顺序又是怎么样的。

首先,很容易理解,默认绑定的优先级是最低的。这是因为只有在无法应用其他this绑定规则的情况下,才会调用默认绑定。那隐式绑定和显式绑定呢?还是上代码吧,代码可从来不会说谎。

function speak() { 
    console.log(this.name) 
} 
 
var obj1 = { 
    name: 'obj1', 
    speak: speak 
} 
var obj2 = { 
    name: 'obj2' 
} 
 
obj1.speak() // obj1 (1) 
obj1.speak.call(obj2) // obj2 (2)

所以在上面代码中,执行了obj1.speak(),speak函数内部的this指向了obj1,因此(1)处代码输出的当然就是obj1,但是当显式绑定了speak函数内的this到obj2上,输出结果就变成了obj2,所有从这个结果可以看出显式绑定的优先级是要高于隐式绑定的。事实上我们可以这么理解obj1.speak.call(obj2)这行代码,obj1.speak只是间接获得了speak函数的引用,这就有点像前面所说的隐式绑定丢失了上下文。好,既然显式绑定的优先级要高于隐式绑定,那么接下来再来比较一下new 绑定和显式绑定。

function foo(something) { 
    this.a = something 
} 
 
var obj1 = {} 
var bar = foo.bind(obj1)  // 返回一个新函数bar,这个新函数内的this指向了obj1  (1) 
bar(2) // this绑定在了Obj1上,所以obj1.a === 2 
console.log(obj1.a) 
 
var baz = new bar(3)  // 调用new 操作符后,bar函数的this指向了返回的新实例baz  (2) 
 
console.log(obj1.a) 
console.log(baz.a)

我们可以看到,在(1)处,bar函数内部的this原本指向的是obj1,但是在(2)处,由于经过了new操作符调用,bar函数内部的this却重新指向了返回的实例,这就可以说明new 绑定的优先级是要高于显式绑定的。

至此,四种绑定规则的优先级排序就已经得出了,分别是

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

箭头函数中的this绑定

箭头函数是ES6里一个重要的特性。

箭头函数的this是根据外层的(函数或者全局)作用域来决定的。函数体内的this对象指的是定义时所在的对象,而不是之前介绍的调用时绑定的对象。举一个例子

var a = 1 
var foo = () => { 
    console.log(this.a) // 定义在全局对象中,因此this绑定在全局作用域 
} 
 
var obj = { 
    a: 2 
} 
foo() // 1 ,在全局对象中调用 
foo.call(obj) // 1,显示绑定,由obj对象来调用,但根本不影响结果

从上面这个例子看出,箭头函数的 this 强制性的绑定在了箭头函数定义时所在的作用域,而且无法通过显示绑定,如apply,call方法来修改。在来看下面这个例子

// 定义一个构造函数 
function Person(name,age) { 
    this.name = name 
    this.age = age  
    this.speak = function (){ 
        console.log(this.name) 
        // 普通函数(非箭头函数),this绑定在调用时的作用域 
    } 
    this.bornYear = () => { 
        // 本文写于2016年,因此new Date().getFullYear()得到的是2016 
        // 箭头函数,this绑定在实例内部 
        console.log(new Date().getFullYear() - this.age) 
        } 
    } 
} 
 
var zxt = new Person("zxt",22) 
 
zxt.speak() // "zxt" 
zxt.bornYear() // 1994 
 
// 到这里应该大家应该都没什么问题 
 
var xiaoMing = { 
    name: "xiaoming", 
    age: 18  // 小明永远18岁 
} 
 
zxt.speak.call(xiaoMing) 
// "xiaoming" this绑定的是xiaoMing这个对象 
zxt.bornYear.call(xiaoMing) 
// 1994 而不是 1998,这是因为this永远绑定的是zxt这个实例

因此 ES6 的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this ,具体来说就是,箭头函数会继承 外层函数调用的this绑定 ,而无论外层函数的this绑定到哪里。

小结

以上就是javascript中所有this绑定的情况,在es6之前,前面所说的四种绑定规则可以涵盖任何的函数调用情况,es6标准实施以后,对于函数的扩展新增了箭头函数,与之前不同的是,箭头函数的作用域位于箭头函数定义时所在的作用域。

而对于之前的四种绑定规则来说,掌握每种规则的调用条件就能很好的理解this到底是绑定在了哪个作用域。


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn