js面试题:this到底指向谁

请用一句话总结this指向

谁调用它,this就指向谁。

this的指向,是在调用函数时根据执行上下文所动态决定的。

具体环节和规则如下:

  • 在函数体中,简单调用该函数时(非显式/隐式绑定下),严格模式下this绑定到undefined,否则绑定到全局对象window/global;

  • 一般构造函数new调用,绑定到新创建的对象上;

  • 一般由上下文对象调用,绑定在该对象上;

  • 箭头函数中,根据外层上下文绑定的this决定指向

实战例题分析

例题组合1:全局环境下的this

函数在浏览器全局环境中被简单调用,非严格模式下this指向window;在use strict指明严格模式的情况下就是undefined。

function f1(){

	console.log(this)  //window

}

function f2(){

	‘use strict’

	console.log(this)  //undefined

}
const foo = {

	bar: 10,

	fn: function(){

    console.log(this)          // 这里的this仍然指向window,虽然fn函数在foo对象中作为方法被引用,但是在赋值给fn1后,fn1的

    console.log(this.bar)	     // 执行仍然是在window的全局环境中。因此此时输出window和undefined。

	}													 // console.log(window)   console.log(window.bar)

}

var fn1 = foo.fn

fn1()                    // window undefined  
const foo = {

	bar: 10,
	
	fn: function(){  	           // 此时this指向的是最后调用它的对象
	
    console.log(this)          // {bar:10,fn:f}

    console.log(this.bar)	     // 10
	
	}													

}
foo.fn()

结论:在执行函数时,如果函数中的this是被上一级的对象所调用,那么this指向的就是上一级的对象;否则指向全局环境。

例题组合2:上下文对象调用中的this
const student = {

	name: 'Lucas',

	fn: function() {

	return this

}	
  
}

console.log(studen.fn() === student)  //true

当存在更复杂的调用关系时:

const person = {

	name: 'Lucas',
  
  brother:{
  
  	fn: function() {
      
      name: 'mike'

			return this.name

		}	
    
  }

}
console.log(person.brother.fn())  // mike this指向最后调用它的对象
const o1 = {

	text: 'o1',

	fn: function () {

	return this.text      

	}

}

const o2 = {

	text: 'o2',

	fn: function () {

	return o1.fn()   

	}

}

const o3 = {

	text: 'o3',

	fn: function () {

	var fn = o1.fn         //裸奔调用

	return fn()   

	}

}

console.log(o1.fn()).        // o1

console.log(o2.fn())		// o1

console.log(o3.fn())  	 // undefined

如果想让console.log(o2.fn())输出o2要怎么做?(不能使用bind/call/apply)

const o1 = {

	text: 'o1',
	
	fn: function () {
	
	return this.text      
	
	}

}

const o2 = {

	text: 'o2',
	
	fn: o1.fn       // this指向最后调用它的对象

}
例题组合3:bind/call/apply 改变this指向
bind/call/apply 三个⽅法的区别

他们都是⽤来改变相关函数 this 指向的,但是 call/apply 是直接进⾏相关函数调⽤;bind 不会执⾏相关函数,⽽是返回⼀个新的函数,这个新的函数已经⾃动绑定了新的 this 指向,开发者需要⼿动调⽤即可。再具体的 call/apply 之间的区别主要体现在参数设定上。

用代码来总结

const target = {}

fn.call(target,'arg1','arg2')

相当于

const target = {}

fn.apply(target,[arg1],[arg2])

相当于

const target = {}

fn.bind(target,'arg1','arg2')()
  • fn.call(obj, arg1, arg2, ...),调用一个函数, 具有一个指定的this值和分别地提供的参数(参数的列表)。

  • fn.apply(obj, [argsArray]),调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组对象)提供的参数。

const foo = {
  name: 'lucas',
  logName: function() {
    console.log(this.name)
  }
}
const bar = {
  name: 'mike'
}
console.log(foo.logName.call(bar))  // mike

注意:对 call/apply/bind 的⾼级考察往往会结合 构造函数以及组合式实现继承。

例题组合4:构造函数和this
function Foo(){
	this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar) // Lucas

这样的场景往往伴随着下一个问题,new操作符调用构造函数具体做了什么?

  • 创建一个新的对象
  • 将构造函数的this指向这个新对象
  • 为这个对象添加属性、方法等
  • 最终返回新对象

以上过程也可以用代码表述:

var obj = {}

Obj.proto = Foo.prototype

Foo.call(obj)

需要指出的是,如果在构造函数中出现了显示return的情况,那么需要注意两种场景:

Function Foo(){
	this.user = "Lucas"
	const o ={}
	return o 
}
const instance = new Foo()
console.log(instance.user) // undefined  此时返回的是空对象
Function Foo(){
	this.user = "Lucas"
	return 1 
}
const instance = new Foo()
console.log(instance.user) // Lucas 此时返回的目标对象实例this

结论:如果构造函数中显示返回一个值,并且返回的是一个对象,那么this就指向这个返回的对象;如果返回的不是一个对象,那么this仍然指向实例

例题组合5:箭头函数的this指向

结论:箭头函数使用this不适用以上标准准则,而是根据外层(函数或者全局)上下文来决定。

const foo ={
	fn: function (){
		setTimeout(function(){
			console.log(this)
		})
	}
}
console.log(foo.fn())

这道题中,this出现在setTimeout()中的匿名函数里,因此this指向window对象。如果需要this指向foo这个object对象,可以巧用箭头函数去解决:

const foo ={
	fn: function (){
		setTimeout(() => {
			console.log(this)
		})
	}
}
console.log(foo.fn()) // {fn: f}
例题组合6:this优先级相关

我们常常把通过call、apply、bind、new对this绑定的情况称为显式绑定;根据调用关系确定的this指向成为隐式绑定。

那么显示绑定和隐式绑定谁的优先级更高呢?

function foo(a){
	console.log(this.a)
}
const obj1 = {
  a: 1,
  foo: foo
}
const obj2 = {
  a: 2,
  foo: foo
}
obj1.foo.call(obj2)  // 2
obj1.foo.call(obj1)  // 1 

说明:call、apply的显示绑定一般来说优先级更高

function foo(a){
	this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)

上述代码通过bind,将bar函数中的this绑定为obj1对象。执行bar(2)后,obj1.a值为2.即经过bar(2)执行后,obj1对象为:{a:2}

当再使用bar作为构造函数时:

var baz = new bar(3)
console.log(baz.a) //3

bar函数本身是通过bind方法构造的函数,其内部已经对将this绑定为obj1,它再作为构造函数,通过new调用时,返回的实例已经与obj1解绑。

结论:new绑定修改了bind绑定中的this,因此new绑定的优先级比显式bind绑定更高

function foo(){
	return a =>{
		console.log(this.a)
	};
}
const obj1 = {
	a: 2
}
const obj2 = {
	a: 3
}
const bar = foo.call(obj1)
console.log(bar.call(obj2)) // 2

由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。

如果将foo完全写成箭头函数的形式:

var a = 123
const foo = () => a=> {
	console.log(this.a)
}
const obj1 = {
	a: 2
}
const obj2 = {
	a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2)) // 123
const a = 123
const foo = () => a=> {
	console.log(this.a)
}
const obj1 = {
	a: 2
}
const obj2 = {
	a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2)) // undefined

原因是因为使用const声明的变量不会挂载到window全局变量上。因此this指向window时,自然找不到a变量。

典型面试:实现一个bind函数(模拟bind)

Function.prototype.bind = Function.prototype.bind ||
function (context) {
  var me = this;
  var args = Array.prototype.slice.call(arguments,1);
  return function bound(){
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return me.apply(context,finalArgs);
  }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值