「这是我参与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 保持一致!
所以在使用箭头函数前需要注意:
- 可以使用箭头函数。如果函数中原本就不包含 this,或者刚好希望函数内的 this 与外部保持一致时。
- 不能使用箭头函数。不希望函数内的 this 与函数外的 this 保持一致时不使用。
箭头函数的 this 无法被 call、apply、bind 改变。
根据这个特性,箭头函数的底层很有可能是由 bind 实现的
八、函数中的 this 可被替换
函数中的 this 可以按两种方式(三种方法)改变:
- 临时替换函数中的 this
-
- call
- apply
- 永久替换函数中的 this
-
- bind
- 临时替换的两种方法:
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对象
- 使用 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 的问题已经搞定了,祝各位点赞的佬爷新年快乐😄