JavaScript令人心烦的this

前言

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

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

this指向

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

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

普通函数

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

首先是最简单的定义并调用一个普通函数,此时输出this为Window 因为是在浏览器中运行,所以当前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指向,apply,bind,call之间还是有点区别的

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')

apply跟call一样是立即调用,第一个参数是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()
相关推荐
程序员大阳4 小时前
副队长JavaScript教程(49)-主动抛出异常与错误类型
javascript·异常处理·error·throw·错误类型
hhhjhl4 小时前
Protobuf.js 跨端三方库仓颉适配全流程
javascript·网络·udp
康小汪4 小时前
AI 写 Vue 总写成同一个味道:给它装 Skills,装的不是知识是边界
前端·vue.js·人工智能·ai编程·最佳实践
智能运维指南5 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
白山编程大哥6 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
Flynt7 小时前
我把热榜那篇"禁用 axios"逐条实测了一遍:结论大体对,坑在它没讲的地方
前端·javascript·axios
考研保研资料分享7 小时前
四非会计保研中国矿大(北京):七场考核、候补与择校复盘
面试
Csvn8 小时前
bundle优化
前端
slongzhang_8 小时前
linux 递归删除源码文件|递归删除目录下 *.js,但排除 *.min.js
linux·开发语言·javascript
北冥有鱼8 小时前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js