引言
而在面试中,经常会被面试官问到各种this指向问题,稍有不注意就容易就会回答错误,在我过往的面试经历中,大部分公司问的最多代码输出题是是this指向和promise输出结果。看起来这俩很简单,多背多看一些题就可以,但实际面试中面试官会留下各种坑,看错一个符号就会导致面试失败。它不像八股一样,多背就行,需要知道它的各种绑定特点,去理解它所在的一个上下文,接下来就和大家分享一下我是如何去学习this指向的,其实很简单,只要理解几种绑定形式就可以。
什么是this
在JavaScript中,this是一个关键字,用于指代当前执行上下文中的对象。这个对象通常被称为上下文对象 ,它可以是一个普通对象、函数、或者构造函数的实例。this充当了一个占位符,代表了当前代码片段与其所在上下文之间的联系。
this具有动态性,是指它的值在函数调用时才确定,而不是在函数定义时。这导致了this的行为具有上下文相关性,即它的值取决于代码的执行上下文。具体来说:
- 
函数调用方式决定this :在
JavaScript中,this的值取决于函数被调用的方式。它可以是以下几种之一:- 在全局上下文中,
this指向全局对象(通常是window对象)。 - 在对象方法中,
this指向调用该方法的对象。 - 在构造函数中,
this指向新创建的实例对象。 - 在事件处理函数中,
this通常指向触发事件的DOM元素。 
 - 在全局上下文中,
 - 
箭头函数的例外 :箭头函数是
JavaScript中的一个特殊情况。它们不会创建自己的this上下文,而是继承外部函数的this。这使得箭头函数的this是静态的,不会随着调用方式而改变。 
接下来结合一些面试题,一一来看几种绑定形式。
默认绑定
非严格环境下,全局下的this指向window, 而在严格环境下是undefined,不允许this指向全局window
            
            
              js
              
              
            
          
          console.log(this === window) // true
        独立调用
当函数独立调用时,this会指向window
            
            
              js
              
              
            
          
          function foo() {
	console.log(this === window)  // true
}
foo()
        看这段代码
            
            
              js
              
              
            
          
          var a = 'ikun'
let b = 'jntm'
function foo() {
	console.log(this.a) // ikun
	console.log(this.b) // undefined
}
foo()
        由于foo是在全局环境下调用,他的this指向window ,也就是访问window.a。那为什么this.b为undefined?
在ES5中,顶层对象的属性和全局变量是等价的,var 命令和 function 命令声明的全局变量,自然也是顶层对象。但ES6规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性,但 let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
❗️这里我之前面试踩过雷,没有看清变量是let还是var声明,大家在看的时候要注意这种细节
再看一个独立调用例子
            
            
              js
              
              
            
          
          var a = 'ikun'
const obj ={
	a: 'jntm',
	foo: function() {
		function fn() {
			console.log(this)
			console.log(this.a)
		}
		return fn()
	}
}
obj.foo()
        在foo函数执行时,此时的this指向的是obj,但是在函数内部独立调用了一个函数fn,此时this会指向window
作为方法调用
当函数作为对象的属性方法调用时,this会绑定到这个对象,也就是谁调用就指向谁
            
            
              js
              
              
            
          
          var a = 'ikun'
var obj = {
	a: 'jntm',
	foo: function() {
		var a = 'jnssssztm'
		console.log(this) // {a: jntm, foo: ƒ}
		console.log(this.a) // jntm
	}
}
obj.foo()
        foo函数在obj下调用,所以此时的this指向obj对象,也就输出jntm。
如果函数调用前面有多个对象,this指向离自己最近的那个对象
            
            
              js
              
              
            
          
          function fn(){
	console.log(this.a)
}
const obj1 = {
	a: 'ikun',
	foo: fn
}
const obj2 = {
	a: 'jntm',
	o: obj1
}
obj2.o.foo() // ikun
        通过o调用obj1下的foo,this会指向o也就是obj1
立即执行函数
立即执行函数也就是定义后立刻执行的匿名函数,在立即执行函数内this指向window
            
            
              js
              
              
            
          
          var a = 'ikun'
const obj = {
	a: 'jntm',
	foo: function() {
		(function() {
			console.log(this) // window
			console.log(this.a) // ikun
		})()
	}
}
obj.foo()
        隐式绑定
在某些特殊情况下会存在this丢失的问题,常见的就是将调用函数作为参数传递或者变量赋值给另外一个变量,此时this指向window
比如这样,通过一个变量fn1来接收函数,此时指向window,也就输出window、ikun
            
            
              js
              
              
            
          
          var bar = 'ikun'
const foo = {
	bar: 'jntm',
	fn: function() {
		console.log(this)
		console.log(this.bar)
	}
}
var fn1 = foo.fn
fn1()
        看个例子
            
            
              js
              
              
            
          
          const obj1 = {
	text: 1,
	fn: function(){
		return this.text
	}
}
const obj2 = {
	text: 2,
	fn: function(){
		return obj1.fn()
	}
}
const obj3 = {
	text: 3,
	fn: function(){
		var fn1 = obj1.fn
		return fn1()
	}
}
console.log(obj1.fn())
console.log(obj2.fn())
console.log(obj3.fn())
        这是经典的隐式绑定题
- 第一个
obj1很简单,fn调用时没有其他操作只是访问了this.text,此时的this指向obj1所以输出1 - 第二个在执行
fn时,返回的是obj1.fn(),可以理解为obj2.fn().obj1.fn(),此时和第一个执行一样,也是在obj1下执行,最后输出1 - 第三个是一个隐式丢失,在fn内部用一个变量
fn1保存obj1.fn函数,最后再将函数返回,此时this就指向window,也就相当于在全局环境下执行function(){return this.text},最后输出为undefined 
我们稍作改变一下,再看看输出结果
            
            
              js
              
              
            
          
          const obj1 = {
	text: 1,
	fn: function(){
		return this.text
	}
}
const obj2 = {
	text: 2,
	fn: obj1.fn
}
obj2.fn()
        此时输出结果为2,在执行fn的时候,是将obj1.fn挂载到obj2的fn上,并没有改变this指向,也就相当于
            
            
              js
              
              
            
          
          const obj2 = {
	text: 2,
	fn: function() {
		return this.text
	}
}
        看懂这个例子后再看一个类似的,
            
            
              js
              
              
            
          
          var a = 'ikun'
function foo() {
	console.log(this.a)
}
function foo2() {
	foo()
}
const obj = {
	a: 1,
	foo3: foo2
}
obj.foo3() // ikun
        显示绑定call、apply、bind
通过call、apply、bind方法强制改变this指向,让它指向我们指定的对象,这里要注意call、apply、bind三个方法改变this指向的区别!
总结就是call、apply会直接进行函数调用,bind不会立即执行函数,而是返回一个新的函数,返回的这个新函数已经自动绑定了新的this。在传参上,call、bind都是接受多个参数,apply接受一个数组。
正常的绑定
            
            
              js
              
              
            
          
          const var = {
	name: 'ikun',
	foo: function() {
		console.log(this.name)
	}
}
const var = {
	name: 'tkl'
}
obj.foo.call(obj2)
        这里输出结果为tkl,不难理解,函数foo在调用时通过call改变了this指向,指向了obj2。
需要注意的是,如果吧null、undefined作为this传入call、apply、bind,此时不会改变的
            
            
              js
              
              
            
          
          var a= 'ikun'
function fn() {
	console.log(this.a)
}
fn.call(null) // ikun
        多个bind同时改变,最终的this由第一次bind决定【这是一道面试题】
            
            
              js
              
              
            
          
          var obj = {
	name: 'ikun',
	foo: function() {
		console.log(this.name)
	}
}
var obj2 = {
	name: 'tkl'
}
var obj3 = {
	name: 'hcy'
}
obj.foo.bind(obj2).bind(obj3)()
        最后输出结果tkl,也就是第一次bind(obj2)的结果
setTimeout和setInterval
setTimeout() 执行的代码是从一个独立于调用setTimeout的函数的执行环境中调用的,它将默认为 window
            
            
              js
              
              
            
          
          var name = 'ikun'
const obj = {
	name: 'jntm',
	foo: function(){
		setTimeout(function() {
			console.log(this.name)
		})
	}
}
obj.foo() // ikun
        构造函数绑定
函数可以作为构造函数使用new创建对象,此时this会发生改变,回顾一下new关键字做了什么操作
new操作符的执行过程:
- 创建一个空对象
 - 设置原型,将构造函数的原型指向空对象的 
prototype属性。 - 将 
this指向这个对象,通过apply执行构造函数。 - 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象
 
需要注意的是区分构造函数的返回值,看个例子
            
            
              js
              
              
            
          
          function Foo() {
	this.name = 'ikun'
	this.age = 66
	const obj = {
		name: 'tkl'
	}
	return obj
}
const foo = new Foo()
console.log(foo.name) // tkl
console.log(foo.age) // undefined
        在构造函数内返回了一个对象,此时的实例foo就指向返回的obj,所以输出tkl,也没有age属性。
如果没有返回或者返回是一个原始类型,就指向实例
            
            
              js
              
              
            
          
          function Foo() {
	this.name = 'ikun'
	this.age = 66
	const obj = {
		name: 'tkl'
	}
	return name
}
const foo = new Foo()
console.log(foo.name) // ikun
console.log(foo.age) // 66
        总结:如果构造函数中返回一个对象,那么this就指向这个对象,如果返回基础数据或者没有返回,this就指向实例。
箭头函数绑定
ES6新增的箭头函数是没有this的,它的this由外层上下文来决定的。【面试基础八股】
            
            
              js
              
              
            
          
          const obj = {
	name: 'ikun',
	foo: () => {
		console.log(this)
	}
}
obj.foo()
        foo为箭头函数,此时this为window
看这道题,最后输出多少
            
            
              js
              
              
            
          
          var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();
        obj.say隐式绑定到o上,此时this为window,执行函数o,内部执行f1箭头函数,由于此时上下文为window,所以箭头函数的this指向window,最后输出1111,window- 通过对象调用执行
say函数,此时this指向obj,所以箭头函数this指向obj,最后输出111 {pro: {...}, say: ƒ} - 也是通过对象调用执行
getPro函数,它是一个箭头函数,此时this为window