写在前面
大家好,我是一溪风月🤠,一名前端工程师。在 JavaScript 里,this
的指向问题恐怕是很多开发者绕不开的 "老大难"------ 无论是原生开发场景,还是在 React、Vue 等主流框架的实践中,this
的使用总能让人栽跟头。
这篇文章我会从基础场景到复杂情境,系统拆解this
的绑定规则:从函数调用方式对this
的影响,到箭头函数与普通函数的本质区别,再到框架中常见的this
陷阱(比如类组件中的事件处理、Vue 方法中的this
指向)。
相信看完这篇内容,你不仅能快速判断this
的指向,更能在实际开发中主动掌控它的行为。话不多说,我们直接进入正题!
一.this到底指向什么?
首先我们先来思考一个让人困惑的问题,定义一个函数我们使用三种不同的方式进行调用,它产生了三种不同的结果,以上的案例能够给我们什么启示哪?
- 函数在调用时,JavaScript会默认给this绑定一个值。
- this的绑定和定义的位置(编写的位置)没有关系。
- this的绑定和调用方式以及调用的位置有关系。
- this是在运行时被绑定的。
那么this到底指向的什么东西哪?以下是this常见的绑定规则。
- 绑定一:默认绑定
- 绑定二:隐式绑定
- 绑定三:显示绑定
- 绑定四:new绑定
二.默认绑定
默认绑定就是直接对函数进行调用,这种方式我们可以称之为独立函数调用,此时函数中的this
指向的是window。
js
function foo () {
console.log("打印foo函数", this)
}
foo() // 指向的是window
函数定义在对象中但是独立进行调用,依然指向的是window和函数定义的位置没有关系。
js
let obj = {
name: "zzz",
foo: function () {
console.log(this)
}
}
let baz = obj.foo
baz() // this指向的是window
💡严格模式下,独立函数调用的函数中的this指向的是undefined
js
"use strict"
// 定义函数
function foo () {
console.log(this)
}
foo() // 此时this指向的是undefined
通过高阶函数的方式进行函数的调用,被调用函数中的this
依然指向的是window
js
function fn () {
console.log(this)
}
function foo (callback) {
callback && callback()
}
foo(fn) // 此时打印的this指向的window

那么我们来总结一下💡:默认绑定(独立函数调用)在严格模式下指向的是
undefined
在非严格模式下指向的是window
并且跟this编写的位置没有关系,无论编写的位置是在对象中,还是通过高阶函数的方式来进行调用其实this指向的都是window
三.隐式绑定
除了上述内容,另外一个比较常用的绑定方式就是通过对象进行调用,也就是它的调用位置中是通过某个对象对函数发起的调用,这个时候this指向的是发起调用的这个对象。
js
function foo () {
console.log(this)
}
let obj = {
bar: foo
}
console.log(obj.bar()); // this指向的是foo这个对象。
四.new绑定
在JavaScript中的函数可以当一个类的构造函数来使用,也就是使用new关键字,首先我们先看下当我们进行实例化的时候究竟做了什么事情。
- 创建一个新的空对象。
- 将this指向这个空对象。
- 执行函数体中的代码。
- 没有显式返回非空对象的时候会默认返回这个对象。
js
function foo (name) {
this.name = name
console.log(this, "打印this数据信息")
}
let obj = new foo("why")

五.显式绑定
隐式绑定有一个前提条件:
- 必须在调用的对象内部有一个对函数的引用(比如一个属性)
- 如果没有这样的引用,在进行调用的时候,会找不到该函数的错误。
- 正是通过这个引用,间接的将this绑定到了这个对象上。
如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么办?
JavaScript中所有的函数都可以使用call
和apply
方法
- 第一个参数是相同的,要求传入一个对象,这个对象的作用是什么?这是给this准备的,在调用这个函数的时候,会将this绑定到这个传入的对象上。
- 后面的参数,apply为数组,call为参数列表。
显式绑定指的是开发者自己手动去对this
进行绑定,常见的显式绑定有三种方式分别是call``apply``bind
三种方式进行绑定和调用。
- 使用
call
进行this的显示绑定:第一个参数绑定this,第二个参数传递额外参数以多参数的形式进行传递,会作为实参。
js
let obj = {
name: "zzz",
age: 22
}
function foo () {
console.log(this)
}
foo.call(obj)

- 使用
apply
进行this的显式绑定:第一个参数绑定this,传入额外的实参以数组的形式。
js
let obj = {
name: "zzz",
age: 22
}
function foo () {
console.log(this)
}
foo.apply(obj) // this指向的是函数绑定的对象

- 如果当我们在调用的时候不希望
obj
身上有函数,这个时候可以使用bind
进行this的绑定:会返回一个绑定函数(怪异函数对象),通过返回的值进行手动调用,第一个参数绑定this,第二个参数以多参数的形式进行实参的传递。
js
let obj = {
name: "zzz",
age: 22
}
function foo () {
console.log(this)
}
let bindFn = foo.bind(obj)
bindFn() // this指向的是obj对象
六.内置函数的this思考
其实在很多时候我们不知道某个函数内部做了什么,因此我们不能安全确定某个函数的内部this
的指向。
定时器函数:指向的是window
js
setTimeout(function () {
console.log("定时器函数" + this)
}, 1000)
按钮的点击监听:指向的是按钮btnEle
js
// 点击按钮触发事件
let btnEle = document.querySelector("button")
btnEle.onclick = function () {
console.log("按钮的点击", this)
}
forEach循环:指向的window,也可以通过第二个参数来指定this
js
let name = ['a', 'b', 'c', 'd', 'e']
name.forEach(item => {
console.log("forEach:", this)
})
七.this绑定的优先级比较
通过以上的规则,当我们在项目代码中遇到了上述的情况可以通过上述的规则来进行匹配,但是如果一个函数调用位置应用了多条这样的规则,优先级谁更高哪?
- 默认规则的优先级较低,毫无疑问默认规则的优先级是最低的,因为存在其他规则的时候,就会通过其他规则的方式来绑定this。
- 显式绑定的优先级高于隐式绑定。
js
function foo () {
console.log("foo:", this)
}
var obj = { foo: foo }
obj.foo.apply("abc") // 指向abc
js
function foo () {
console.log("foo:", this)
}
let bar = foo.bind("aaa")
let obj = {
name: "zzz",
baz: bar
}
obj.baz()

- new绑定优先级高于隐式绑定。
js
let obj = {
name: "why",
foo: function () {
console.log(this)
console.log(this === obj)
}
}
new obj.foo()

- new绑定和显示绑定,new不可以和
apply/call
一起使用,new优先级高于bind
绑定。
js
function foo () {
console.log("foo", this)
}
let bindFn = foo.bind("aaa")
new bindFn()
那么我们来总结一下优先级:
- new优先级
- bind
- apply/call
- 隐式绑定
- 默认绑定
八.this规则之外
上述的内容其实我们足以应付平时的开发了,但是其实在规则之外还有一些特殊的情况我们需要注意的。
情况一:显式绑定null/undefined,那么规则使用的是默认绑定。
js
function foo () {
console.log("foo", this)
}
foo.apply(null)
foo.apply(undefined)

在严格模式下的规则:
js
"use strict"
function foo () {
console.log("foo", typeof this)
}
foo.apply("abc")
foo.apply(null)
foo.apply(undefined)

情况二:间接函数引用,创建函数间接引用,这种情况使用默认绑定规则
js
var obj1 = {
name: "obj1",
foo: function () {
console.log("foo", this)
}
}
var obj2 = {
name: "obj2",
};
(obj2.foo = obj1.foo)() // this->window
需要说明间接函数引用本质上还是一种独立函数调用!
九.箭头函数arrow function
箭头函数是ES6之后增加的一种编写函数的方法,并且它比表达式要更加的简洁。
- 箭头函数不会绑定
this
和arguments属性
- 箭头函数不能作为构造函数使用(不能和new一起使用,全抛出错误)
js
const foo = (name, age) => {
console.log("箭头函数的函数体")
console.log(name, age)
}
箭头函数的精简写法:
- 如果只有一个参数的话箭头函数的小括号可以省略
js
const foo = name => {
console.log("箭头函数的函数体")
console.log(name)
}
- 如果函数体中只有一行执行代码,这行代码的结果会自动作为整个函数的返回值。
js
const foo = age => age*2;
- 如果函数体中只有一行执行代码,那么
{}
可以省略,一行代码中不能带return关键字,如果省略需要带return一起省略。
js
var nums = ['a', 'b', 'c', 'd', 'e']
nums.filter(item => {
return item === 'a'
})
// 优化后
var nums = ['a', 'b', 'c', 'd', 'e']
nums.filter(item => item === 'a')
- 如果默认返回值是一个对象,那么这个对象必须添加小括号(react中使用较多)
js
const foo = () => ({ name: "zzz", age: 12 })
小案例练习:箭头函数实现nums的所有偶数平方的和
js
var nums = [20, 30, 11, 15, 111]
var result = nums.filter(item => item % 2 === 0)
.map(item => item ** 2)
.reduce((acc, item) => acc + item, 0)
console.log(result)
// 结果:1300
十.箭头函数的this
我们知道普通函数是有this的,但是其实在箭头函数中压根没有this,我们在箭头函数中打印出来的this其实是上层作用域中的this,this这个变量会通过作用域一层一层的进行查找,如下述代码就是找到了window
js
function foo () {
console.log("foo", this)
}
foo() // window
十一.this面试题解析
this面试题一:
js
var name = "window"
var person = {
name: "person",
sayName: function () {
console.log(this.name)
}
}
function sayName () {
var sss = person.sayName
sss() //绑定:默认绑定 window
person.sayName(); //绑定:隐式绑定 person
(person.sayName)(); //绑定: 隐式绑定 person
(b = person.sayName)() //绑定: 间接函数引用 window
}
sayName()
this面试题二:
js
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1(); // person1
person1.foo1.call(person2); // person2
person1.foo2(); // window
person1.foo2.call(person2); // window
person1.foo3()(); // window
person1.foo3.call(person2)(); // window
person1.foo3().call(person2); // person2
person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
this面试题三:
js
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1() // person1
person1.foo1.call(person2) // person2
person1.foo2() // person1
person1.foo2.call(person2) // person1
person1.foo3()() // window
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
this面试题四:
js
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1() // person1
person1.foo1.call(person2) // person2
person1.foo2() // person1
person1.foo2.call(person2) // person1
person1.foo3()() // window
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
十一.总结
这篇文章到这里就结束了😈,这篇文章我们学习了this和箭头函数的内容,这篇文章我们首先讲解了this的绑定的几种规则,分别包括,默认绑定,隐式绑定,new绑定,显式绑定等等内容,除了常规的绑定之外,我们了解了this绑定规则之外的一些内容,最后我们学习了ES6的箭头函数以及箭头函数中的this。