JavaScript令人心烦的this

前言

本文章源自《JavaScript知识小册》专栏,感兴趣的话还请关注点赞收藏.

上一篇文章:《JavaScript中的作用域

this指向

JavaScript中的this指向是面试常考问题,同时也是非常令人心烦的,因为它跟Java这种纯粹面向对象的语言不太一样,Java中的this永远表示当前对象实例,而JavaScript中的this在不同情况下所指向的也不一样[个人觉得这其实无形中给开发者添加了不必要的心智负担...],所以本文对this在各种应用场景下的指向做一个汇总和梳理。

this具体指向什么值,是在执行时动态决定的,而不是在编码时就已经被定义了。

普通函数

javascript 复制代码
function print() {
    console.log('this is ', this)
}
print()

首先是最简单的定义并调用一个普通函数,此时输出thisWindow [因为是在浏览器中运行,所以当前this指向window全局对象,node.js环境不会指向window]

call/apply/bind

call

arduino 复制代码
function print(){
    console.log('this is ', this)
}

print.call({name: 'Bruse'})

使用call的方式调用函数,可以改变函数中的this指向,这里this则指向了{name: 'Bruse'}这个对象,输出this is {name: "Bruse"}

bind

arduino 复制代码
function print(){
    console.log('this is ', this)
}

print.bind({name: 'Bruse'})()

使用bind修改this指向,同样输出this is {name: "Bruse"},bind的区别在于它其实是返回一个新的函数,并交由调用方决定在什么时候执行,而call是更改this指向同时立即执行函数

下边的示例中bind返回的新函数可以不立即调用

arduino 复制代码
function print() {
    console.log('this is ', this)
}

const printV2 = print.bind({name: 'Bruse'})
console.log('call printV2')
printV2()

输出

kotlin 复制代码
call printV2
this is  {name: "Bruse"}

apply

arduino 复制代码
function print() {
    console.log('this is ', this)
}

print.apply({name: 'Bruse'})

使用apply同样也可以修改this的指向,输出this is {name: "Bruse"}

区别

同样是可以修改this指向,applybindcall之间还是有点区别的

call是立即调用,第一个参数是this指向,然后第二个参数是方法参数列表

arduino 复制代码
function print(age, phone) {
    console.log('this is ', this)
    console.log('age is ', age)
    console.log('phone is ', phone)
}

print.call({name: 'Bruse'},  16, '135xxxxxx')

输出

kotlin 复制代码
this is  {name: "Bruse"}
age is  16
phone is  135xxxxxx

bind是返回新的函数,但调用时机还是由调用方决定,第一个参数是this指向,第二个参数是方法参数列表

arduino 复制代码
function print(age, phone) {
    console.log('this is ', this)
    console.log('age is ', age)
    console.log('phone is ', phone)
}
print.bind({name: 'Bruse'}, 16, '135xxxxxx')()

输出

kotlin 复制代码
this is  {name: "Bruse"}
age is  16
phone is  135xxxxxx

bind传参还可以这么玩

php 复制代码
// print.bind({name: 'Bruse'}, 16, '135xxxxxx')() 

// 还可以是如下的方式

print.bind({name: 'Bruse'})(16, '135xxxxxx')

// or

const printV2 = print.bind({name: 'Bruse'})
printV2(16, '135xxxxxx')

applycall一样是立即调用,第一个参数是this指向,然后第二个参数是一个数组,方法的参数都存放到这个数组中

arduino 复制代码
function print(age, phone) {
    console.log('this is ', this)
    console.log('age is ', age)
    console.log('phone is ', phone)
}

print.apply({name: 'Bruse'}, [16, '135xxxxxx'])

输出

kotlin 复制代码
this is  {name: "Bruse"}
age is  16
phone is  135xxxxxx

对象方法

执行对象方法时,this即当前对象

javascript 复制代码
const man = {
    name: 'Bruse',
    work(){
        console.log(`${this.name} working...`)
    }
}

man.work()

输出Bruse working...

但如果在对象方法中在setTimeout setInterval 等回调代码中调用this,会发现this指向不再是当前对象。

javascript 复制代码
const man = {
    name: 'Bruse',
    work() {
        setTimeout(function () {
            console.log(`${this.name} working...`)
        }, 10)
    }
}

man.work()

输出 working...

千万记住this是在运行时动态决定的,而console.log(...)调用的时候,并不是直接通过调用man对象的work方法,而是在setTimeout触发的回调函数中调用的,所以这个时候this指向的是window对象。

只需要加上

ini 复制代码
window.name = 'Chrome'

输出就会变为Chrome working...

箭头函数

箭头函数里无this。记住这一句话就好了,比如下边的代码

javascript 复制代码
const man = {
    name: 'Bruse',
    work() {
        setTimeout(() => {
            console.log(`${this.name} working...`)
        }, 10)
    }
}

man.work()

同样是在setTimeout中调用console.log,但是这次却输出Bruse working...

这是因为箭头函数中this永远指向的是上级作用域的this,简单点理解就是箭头函数里没有this,那么在调用console.log时,就需要往上级作用域,也就是work方法中找,而work中的this就是man对象,所以顺理成章的this.name就是man.name

作用域回顾传送门

class方法

还有一种则是在class中的方法取this

javascript 复制代码
class People {
    constructor(name) {
        this.name = name
    }
    work(){
        console.log(`${this.name} do work...`)
    }
}

const boy = new People('Bruse')
boy.work()

this指向的是当前class的实例,输出Bruse do work...

当然,如果还是遇到setTimeout这样的,this还是会在work具体被调用的时候动态指向了window

javascript 复制代码
window.name = 'Chrome'

class People {
    constructor(name) {
        this.name = name
    }
    work(){
        console.log(`${this.name} do work...`)
    }
}

const boy = new People('Bruse')

setTimeout(boy.work, 10)

输出Chrome do work...

而如果是箭头函数的话,最后输出Bruse do work...

javascript 复制代码
class People {
    constructor(name) {
        this.name = name
    }
    work  ()  {
        setTimeout(() => {
            console.log(`${this.name} do work...`)
        }, 10)
    }
}

const boy = new People('Bruse')
boy.work()
相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax