在JavaScript中,this指向一直是一个容易搞混的问题,往往在出错的时候还不知道各种原因,今天就来详细研究一下。
1.this
💡什么是this ?
this
是JavaScript的一个关键字,只能在对象内部使用。
在绝大多数情况下,函数的调用方式决定了this
的值(即运行时绑定);而在ES6引入了箭头函数后,this
的值将保持为闭合词法上下文的值。
2.标准函数中的this绑定
💡下面来介绍在5种
this
绑定规则,助你在函数调用时判断this
指向。
2.1 默认绑定
function f1() {
var a = 1;
console.log(this.a);
}
function f2() {
"use strict"
console.log(this);
}
var a = 5;
f1(); // 5
f2(); // undefined
this
默认绑定可以理解为函数调用时无需任何调用前缀的情况,即直接使用而不带任何修饰的函数调用,如上述代码中的f1() 与 f2()
。
通常情况下,默认绑定在非严格模式下一般是在**window**
上,严格模式是为**undefined**
。
2.2 隐性绑定
function add() {
console.log(this.a + this.b);
}
var obj = {a: 1,
b: 2,
add: add
};
var obj1 = {o: obj}
obj.add(); // 3
obj1.o.add(); // 3
如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上。在函数add()
执行的时候有了上下文对象,即obj
,this就绑定到obj上。同时,当函数调用前存在多个对象,即链性关系时,this指向距离自己最近的对象,如obj1.o.add()
。
但如果按照上述所说,隐性绑定中规定上下文对象必须包含我们要调用的函数,可是如此一来扩展和维护性就太差了,为了解决这个问题,我们引入了显性绑定。
2.3 显性绑定
在讲显性绑定前,我们首先去了解几个函数:call()、apply()和 bind()
。它们的作用都是改变函数的this指向。但是,当我们在使用call()
等方法改变this指向时,如果指向参数为null或者undefined,那么this将指向全局对象。
let obj1 = { x: 1 };
let obj2 = { x: 2 };
let obj3 = { x: 3 };
var x = 4;
function fn() {
console.log(this.x)
}
fn.call(obj1); // 1
fn.apply(obj2); // 2
fn.bind(obj3)(); // 3
fn(); // 4
fn.call(undefined); // 4
fn.apply(null); // 4
fn.bind(undefined)(); // 4
由上述代码可知,显性绑定中将隐性绑定中的上下文对象的函数去掉了。
番外 —- call、apply与bind有什么区别?
- calll、apply 与 bind 都用于this绑定,但 call、apply 函数在改变this指向的同时还会执行函数;而 bind 函数在改变this后返回一个全新的绑定函数。
- bind 属于硬绑定,返回的绑定函数的this指向不能再通过 bind、apply 或 call 修改,即
this
被永久绑定;call 与 apply 只适用于当前调用,一次调用后就结束。 - call 和 apply 功能完全相同,但call 从第二个参数后的所有参数都是原函数的参数;而 apply 只接受两个参数,第二个参数必须是数组,该数组包含着原函数的参数列表。
2.4 new绑定
js中用new
修饰的函数就是构造函数,准确点就是函数的构造应用。当我们new
一个函数时,js会做以下工作:
- 创建新对象;
- 继承原函数的原型prototype;
- 将这个新对象绑定到该函数的
**this**
上; - 如果构造器没有手动返回对象,则返回第一步创建的对象。 ```javascript function foo() { this.a = 20; console.log(this); } foo(); // window
var obj = new foo(); // foo{a: 20} console.log(obj.a); // 20
通常来说,使用new调用函数后,函数会以自己的名字命名和创建一个新的对象并返回。但当原函数返回一个对象类型,我们将丢失绑this的新对象,原因在于无法返回新对象。
```javascript
function foo() {
this.a = 20;
return {b: 30}
}
var obj = new foo();
console.log(obj.a); // undefined
console.log(obj) // {b: 30}
2.5 4种绑定优先级
由于显示绑定与new
绑定不能共存,所以绑定优先级如下:
显式绑定 > 隐式绑定 > 默认绑定
new 绑定 > 隐式绑定 > 默认绑定
3.箭头函数中的this
箭头函数中没有this,箭头函数中的this指向取决于外层作用域的this,外层作用域中的this指向谁,箭头函数中的this便指向谁。一般来说,箭头函数在确定了this指向后无法被修改,即使将this传递给call
、bind
、或者apply
来调用箭头函数,它也被忽略。
var foo = (() => {
console.log(this.a)
})
var a = 10;
var obj1 = { a: 20, foo: foo };
var obj2 = { a: 30 };
foo() // 10, this指向window
obj1.foo(); // 10
foo.call(obj2); // 10
当然了,箭头函数的`this`与上级作用域的`this`指向一致,我们可以通过修改外层函数的`this`指向达到间接修改箭头函数`this`的目的。
function foo() {
return () => {
console.log(this.a);
}
}
var obj1 = { a: 20 };
var obj2 = { a: 30 };
foo.call(obj1)(); // 20, this指向obj1
foo.call(obj2)(); // 30, this指向obj2
4.全文总结
通过阅读上述文字,对this的5种绑定场景全部介绍完毕。
通过本文,我们可以知道:
- 默认绑定在严格模式和非严格模式下的
this
会有所不同; - 隐性绑定中
this
指向上下文对象; - 显性绑定中
this
指向的是call
、bind
、或者apply
的第一个参数; - new绑定中
this
指向的是新创建的对象; - 箭头函数中
this
指向取决于外层函数的this
指向。