在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(); // 5f2(); // 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(); // 3obj1.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); // 1fn.apply(obj2); // 2fn.bind(obj3)(); // 3fn(); // 4fn.call(undefined); // 4fn.apply(null); // 4fn.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的新对象,原因在于无法返回新对象。```javascriptfunction foo() {this.a = 20;return {b: 30}}var obj = new foo();console.log(obj.a); // undefinedconsole.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指向windowobj1.foo(); // 10foo.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指向obj1foo.call(obj2)(); // 30, this指向obj2
4.全文总结
通过阅读上述文字,对this的5种绑定场景全部介绍完毕。
通过本文,我们可以知道:
- 默认绑定在严格模式和非严格模式下的
this会有所不同; - 隐性绑定中
this指向上下文对象; - 显性绑定中
this指向的是call、bind、或者apply的第一个参数; - new绑定中
this指向的是新创建的对象; - 箭头函数中
this指向取决于外层函数的this指向。
