面试官:请你说说 this 有几种情况?

267 阅读4分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

这篇文章不仅仅会回答 this 有几种情况,也会给出面试中的常见手写题———手写一个bind

首先,我需要强调的是:

判断 this,一定不要看在哪里定义,而是要看在哪里、如何被调用

OK,对于 this 有几种情况这个问题,我们先给出结论是8种。

本篇部分知识或相关原理之前在面向对象的特点一文中有讲过,可👉点击前往观看

一、this 指向 . 前的对象

const obj = {
    name: '小明',
    getName: function() {
  	console.log(this.name)
    }
}

obj.getName() // "小明"

二、new 构造函数() 中 this 指向新创建的对象

function Person(name, age) {
    this.name = name
    this.age = age
    this.getInfo = function() {
  	console.log(this.name, this.age)
    }
}

const xiaoming = new Person('小明', 18)
xiaoming.getInfo() // "小明", 18

三、构造函数的prototype上挂载的方法中的this指向将来调用 .前的对象

function Person(name, age) {
    this.name = name
    this.age = age
}

Person.prototype.getInfo = function() {
    console.log(this.name, this.age)
}

const xiaoming = new Person("小明", 18)
xiaoming.getInfo() // "小明", 18

四、function函数、匿名函数自调和回调函数中的 this 指向 window

严格模式下为 undefined

function fun() {
    console.log(this) // window
}

(function() {
    console.log(this) // window
})()

setTimeout(function() {
    console.log(this) // window
}, 0)

[1,2].forEach(item => {
    console.log(this) // window
})

五、DOM 事件处理函数中的 this 指向正在触发事件的 .前的 DOM 元素对象

在这种情况下需要使用 this 时,一定不要使用箭头函数来代替 function 函数

btn.onclick = function() {
    console.log(this)
}

btn.addEventListener("click", function() {
    console.log(this)
})

六、Vue 中的 this 默认指向当前 vue 对象

<script>
  export default {
    methods: {
    	fun() {
      	console.log(this)
      }
    }
  }
</script>

七、箭头函数中的 this 指向当前函数之外最近的作用域中的 this

一般来讲大多数function函数或匿名函数都可以用箭头函数简化。但也有例外:

// 箭头函数最近的作用域就是window
const xiaoming = {
    name: '小明',
    getName: () => { 
        console.log(this) // window
    }
}

在上面的例子中,我们就不能像之前一样通过this.name访问到xiaoming的内部属性name

这是因为箭头函数可让函数内的 this 与函数外的 this 保持一致!

所以在使用箭头函数前需要注意:

  1. 可以使用箭头函数。如果函数中原本就不包含 this,或者刚好希望函数内的 this 与外部保持一致时。
  2. 不能使用箭头函数。不希望函数内的 this 与函数外的 this 保持一致时不使用。

箭头函数的 this 无法被 call、apply、bind 改变。

根据这个特性,箭头函数的底层很有可能是由 bind 实现的

八、函数中的 this 可被替换

函数中的 this 可以按两种方式(三种方法)改变:

  • 临时替换函数中的 this
    • call
    • apply
  • 永久替换函数中的 this
    • bind
  1. 临时替换的两种方法:

call 方法适用于已知实参数量

function bonusCounter(bonus1, bonus2, bonus3) {
    console.log(`${this.name}合计有${bonus1 + bonus2 + bonus3}元`)
}

let xiaoming = {
    name: '小明'
}

// 通过调用函数的原型方法 call
// 传入函数中要使用的 this 对象 小明
// 一个一个的传入参数
bonusCounter.call(xiaoming, 1000, 2000, 3000) // 小明合计有6000元

apply 方法适用于未知实参长度的数组

function bonusCounter(bonus1, bonus2, bonus3) {
    console.log(`${this.name}合计有${bonus1 + bonus2 + bonus3}元`)
}

let xiaoming = {
    name: '小明'
}

// 通过调用函数的原型方法 apply
// 传入函数中要使用的 this 对象 小明
// 传入一个数组参数
bonusCounter.apply(xiaoming, [100, 200, 300]) // 小明合计有600元

call、apply 方法只是临时替换掉 function 内部的 this,下次调用bonusCounter内部的this仍是window对象

  1. 使用 bind 创建一个永久被替换了 this 的副本函数
function bonusCounter(bonus1, bonus2, bonus3) {
    console.log(`${this.name}合计有${bonus1 + bonus2 + bonus3}元`)
}

let xiaoming = {
    name: '小明'
}

// 通过调用函数的原型方法 bind
// 传入函数中要替换的 this 对象 小明
// 获得一个小明专属的计算函数
const xiaomingBonusCounter = bonusCounter.bind(xiaoming)
// this 对象已经被永久替换,直接传入参数即可
xiaomingBonusCounter(1000, 2000, 3000) //  小明合计有6000元

console.log(xiaomingBonusCounter === bonusCounter) // false

通过bind后获得的函数是一个新的函数,这个函数内部的 this 被永久绑定为xiaoming对象,无法被再次改变。

值得注意的是,箭头函数中无法修改执行时的this,但是传入的后续参数与 function 函数效果一样

const fn = (p, q) => {
    console.log(this, p, q)
}

const xiaoming = { name: '小明' }
fn.call(xiaoming, 1, 2) // window, 1, 2

fn.apply(xiaoming, [1, 2]) // window, 1, 2

const temp = fn.bind(xiaoming, 1, 2)
temp() // window, 1, 2

实现 bind 方法

Function.prototype.myBind = function() {
    let fn = this // 拿到将来调用 myBind 方法的函数
    let args = [...arguments] // 拿到传入的参数数组
    const ctx = args.shift() // 获得 myBind 第一个函数的对象,将来调用的 this。
  // 那么剩下的 args 就是剩余的参数
    return function() { // 返回一个函数
        fn.call(ctx, ...args, ...arguments)
        // 最后调用返回的函数其实是调用这个 fn 通过 call来改变 this
    }
}

// 对应到 myBind 中的 ctx。也就是被改变后的 this
const obj = {
    name: '小明'
}

function fun(age, className) {
    console.log(`${this.name}的年龄是${age}岁,是${className}班学生`)
}

const temp = fun.myBind(obj, 18) // 传递将要改变为的 this 对象以及固定 age 参数

temp("三") // 小明的年龄是18岁,是三班学生

OK,面试中常问到的 this 以及手写 bind 的问题已经搞定了,祝各位点赞的佬爷新年快乐😄