先记住这个答案
普通函数的 this 是调用时才确定的,看它是被 new 调用、用 call 指定,还是写成 obj.fn()。箭头函数不一样,它根本不创建自己的 this,函数体里的 this 会像普通变量一样往外找,用的是外层函数的 this。所以箭头函数的 this 在写代码的位置就定了,之后怎么调用、用 call 或 bind 传什么对象都改不了。这就是「词法绑定」的意思,词法就是按代码写的位置来确定。
- 箭头函数的
this等于外层函数的this call、apply、bind改不了箭头函数的this,参数照常传- 对象字面量不是作用域,里面写箭头方法拿不到对象
- 箭头函数不能
new,也没有prototype
call 为什么改不了箭头函数的 this
在一个方法里返回箭头函数,然后试着用 call 换一个对象调用它。
thisJavaScriptconst counter = {
value: 2,
makeAdder() {
return (step) => this.value + step // this 是 makeAdder 调用时的 this
}
}
const add = counter.makeAdder()
console.log(add(3))
console.log(add.call({ value: 100 }, 3)) // 传进去的对象被忽略查看输出与解释
5
5counter.makeAdder() 调用时,makeAdder 的 this 是 counter。返回的箭头函数没有自己的 this,里面的 this 就是这个 counter。后面 add.call({ value: 100 }, 3) 想换对象,但箭头函数压根没有 this 可以换,第一个参数被直接忽略,第二个参数 3 照常传给了 step。
bind 也一样。对箭头函数 bind 会返回一个新函数,预置的参数有效,this 无效。
对象字面量里写箭头方法的坑
最常见的误用是把对象的方法写成箭头函数,以为 this 会指向这个对象。
thisJavaScriptfunction create() {
return {
name: 'obj',
arrow: () => this.name, // 外层是 create,this 是 create 的 this
normal() {
return this.name // 普通方法,this 是调用它的对象
}
}
}
const o = create.call({ name: 'outer' })
console.log(o.arrow(), o.normal())查看输出与解释
outer obj对象字面量的花括号不是函数,不会产生 this。所以 arrow 往外找,找到的是 create 这次调用的 this,也就是传进去的 { name: 'outer' }。如果对象直接写在模块顶层,外层就是模块顶层,this 是 undefined,调用时会报错。需要用到对象本身的方法,要写成普通方法。
什么时候用箭头函数,什么时候别用
箭头函数最适合写在方法内部的回调里。比如类的方法里写 setTimeout(() => this.refresh(), 1000),回调里的 this 就是实例,不用再写 const self = this 或者 .bind(this)。数组的 map、Promise 的 then 里也一样。
- 类字段箭头函数的取舍
- React 类组件里常写
handleClick = () => { this.setState(...) },这是类字段,每创建一个实例都会新建一个箭头函数,this固定是这个实例,传给onClick也不会丢。代价是函数挂在实例上而不是原型上,实例多了会多占内存,子类也没法通过super.handleClick()调用它。 反过来,需要动态this的地方别用箭头函数:对象方法、原型方法、需要new的构造函数,以及想靠this拿到绑定元素的 DOM 事件监听。
容易答错的地方
- 想用
bind修箭头函数的this bind对箭头函数的this没用。箭头函数的this不对,只能改它定义的位置,或者换成普通函数。- 把对象的花括号当成一层作用域
{ fn: () => this }里的this和对象没有关系。只有函数(普通函数、方法、类的构造过程)才会提供this,对象字面量只是一个值。- 以为箭头函数的
this是创建时拍下的快照 - 箭头函数读的是外层的
this本身。同一段代码里的箭头函数,外层函数被不同对象调用,新建出来的箭头函数this也不同,上面create.call的例子就是这样。
面试官还会怎么问?
箭头函数能当构造函数吗?
不能。箭头函数没有 prototype 属性,也没有构造能力,用 new 调用会抛 TypeError。
事件监听里用箭头函数会怎样?
addEventListener 调用普通函数时,会把 this 设成绑定监听的元素,也就是 event.currentTarget。换成箭头函数,this 就是外层的,比如组件实例或 undefined。想要元素就用 event.currentTarget,想要实例就用箭头函数。
箭头函数嵌套箭头函数,this 是谁的?
一层层往外找,直到遇到第一个普通函数,用它的 this;一直找到顶层都没有,就用顶层的 this,ES 模块里是 undefined,浏览器普通脚本里是 window。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。