在JavaScript中,this指向一直是一个容易搞混的问题,往往在出错的时候还不知道各种原因,今天就来详细研究一下。

1.this

💡什么是this ?

this是JavaScript的一个关键字,只能在对象内部使用。
在绝大多数情况下,函数的调用方式决定了this的值(即运行时绑定);而在ES6引入了箭头函数后,this的值将保持为闭合词法上下文的值。

2.标准函数中的this绑定

💡下面来介绍在5种this绑定规则,助你在函数调用时判断this指向。

2.1 默认绑定

  1. function f1() {
  2. var a = 1;
  3. console.log(this.a);
  4. }
  5. function f2() {
  6. "use strict"
  7. console.log(this);
  8. }
  9. var a = 5;
  10. f1(); // 5
  11. f2(); // undefined

this默认绑定可以理解为函数调用时无需任何调用前缀的情况,即直接使用而不带任何修饰的函数调用,如上述代码中的f1() 与 f2()
通常情况下,默认绑定在非严格模式下一般是在**window**上,严格模式是为**undefined**

2.2 隐性绑定

  1. function add() {
  2. console.log(this.a + this.b);
  3. }
  4. var obj = {a: 1,
  5. b: 2,
  6. add: add
  7. };
  8. var obj1 = {o: obj}
  9. obj.add(); // 3
  10. obj1.o.add(); // 3

如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上。在函数add()执行的时候有了上下文对象,obj,this就绑定到obj上。同时,当函数调用前存在多个对象,即链性关系时,this指向距离自己最近的对象,obj1.o.add()
但如果按照上述所说,隐性绑定中规定上下文对象必须包含我们要调用的函数,可是如此一来扩展和维护性就太差了,为了解决这个问题,我们引入了显性绑定。

2.3 显性绑定

在讲显性绑定前,我们首先去了解几个函数:call()、apply()和 bind()。它们的作用都是改变函数的this指向。但是,当我们在使用call()等方法改变this指向时,如果指向参数为null或者undefined,那么this将指向全局对象。

  1. let obj1 = { x: 1 };
  2. let obj2 = { x: 2 };
  3. let obj3 = { x: 3 };
  4. var x = 4;
  5. function fn() {
  6. console.log(this.x)
  7. }
  8. fn.call(obj1); // 1
  9. fn.apply(obj2); // 2
  10. fn.bind(obj3)(); // 3
  11. fn(); // 4
  12. fn.call(undefined); // 4
  13. fn.apply(null); // 4
  14. fn.bind(undefined)(); // 4

由上述代码可知,显性绑定中将隐性绑定中的上下文对象的函数去掉了。

番外 —- call、apply与bind有什么区别?

  1. calll、apply 与 bind 都用于this绑定,但 call、apply 函数在改变this指向的同时还会执行函数;而 bind 函数在改变this后返回一个全新的绑定函数。
  2. bind 属于硬绑定,返回的绑定函数的this指向不能再通过 bind、apply 或 call 修改,即this被永久绑定;call 与 apply 只适用于当前调用,一次调用后就结束。
  3. call 和 apply 功能完全相同,但call 从第二个参数后的所有参数都是原函数的参数;而 apply 只接受两个参数,第二个参数必须是数组,该数组包含着原函数的参数列表。

(下一篇会详细讲讲这三个有意思的函数)。

2.4 new绑定

js中用new修饰的函数就是构造函数,准确点就是函数的构造应用。当我们new一个函数时,js会做以下工作:

  1. 创建新对象;
  2. 继承原函数的原型prototype;
  3. 将这个新对象绑定到该函数的**this**上;
  4. 如果构造器没有手动返回对象,则返回第一步创建的对象。 ```javascript function foo() { this.a = 20; console.log(this); } foo(); // window

var obj = new foo(); // foo{a: 20} console.log(obj.a); // 20

  1. 通常来说,使用new调用函数后,函数会以自己的名字命名和创建一个新的对象并返回。但当原函数返回一个对象类型,我们将丢失绑this的新对象,原因在于无法返回新对象。
  2. ```javascript
  3. function foo() {
  4. this.a = 20;
  5. return {b: 30}
  6. }
  7. var obj = new foo();
  8. console.log(obj.a); // undefined
  9. console.log(obj) // {b: 30}

2.5 4种绑定优先级

由于显示绑定与new绑定不能共存,所以绑定优先级如下:
显式绑定 > 隐式绑定 > 默认绑定
new 绑定 > 隐式绑定 > 默认绑定

3.箭头函数中的this

箭头函数中没有this,箭头函数中的this指向取决于外层作用域的this,外层作用域中的this指向谁,箭头函数中的this便指向谁。一般来说,箭头函数在确定了this指向后无法被修改,即使将this传递给callbind、或者apply来调用箭头函数,它也被忽略。

  1. var foo = (() => {
  2. console.log(this.a)
  3. })
  4. var a = 10;
  5. var obj1 = { a: 20, foo: foo };
  6. var obj2 = { a: 30 };
  7. foo() // 10, this指向window
  8. obj1.foo(); // 10
  9. foo.call(obj2); // 10
  1. 当然了,箭头函数的`this`与上级作用域的`this`指向一致,我们可以通过修改外层函数的`this`指向达到间接修改箭头函数`this`的目的。
  1. function foo() {
  2. return () => {
  3. console.log(this.a);
  4. }
  5. }
  6. var obj1 = { a: 20 };
  7. var obj2 = { a: 30 };
  8. foo.call(obj1)(); // 20, this指向obj1
  9. foo.call(obj2)(); // 30, this指向obj2

4.全文总结

通过阅读上述文字,对this的5种绑定场景全部介绍完毕。
通过本文,我们可以知道:

  1. 默认绑定在严格模式和非严格模式下的this会有所不同;
  2. 隐性绑定中this指向上下文对象;
  3. 显性绑定中this指向的是callbind、或者apply的第一个参数;
  4. new绑定中this指向的是新创建的对象;
  5. 箭头函数中this指向取决于外层函数的this指向。