做前端开发的同学,几乎都被this指向“坑过”——明明定义时逻辑没问题,一运行就报undefined;同一个函数,换种调用方式this就“跑偏”;面试时被问起this绑定规则,支支吾吾说不明白。
其实this的核心就一句话:this的指向,不是在函数定义时确定的,而是在函数调用时确定的,谁调用它,他就指向谁(箭头函数除外)。
一、4种核心场景:精准判断this指向
不用死记硬背,结合代码场景理解,每一种场景都是工作/面试高频考点,建议边看边敲代码验证。
场景1:普通函数调用(默认绑定)
函数直接调用,没有任何对象前缀,this默认指向全局对象(浏览器种是window,Node.js中是global);严格模式下,this指向undefined。
// 非严格模式
function sayHi() {
console.log(this) // window (浏览器环境)
console.log(this.name) // 全局变量name Hello
}
window.name = 'Hello'
sayHi() // 直接调用,无对前缀
// 严格模式下
function sayHiStrict() {
“use strict”
console.log(this) // undefined
}
sayHiStrict()
💡坑点:新手容易误导以为this指向函数本身,其实不然,普通调用时,函数与任何对象都无关联,自然绑定全局。
场景2:对象方法调用(隐式绑定)
函数作为对象的属性,通过“对象.方法()”的方式调用,this指向调用该方法的对象。
const user = {
name: '掘金',
greet: function() {
console.log(`你好,${this.name}`) // this的指向user
}
}
user.greet() // 输出: 你好,掘金
// 坑点: 方法赋值后调用,this会“丢失”
const greet = user.greet
greet() // 输出:你好,undefined (此时是普通调用,this指向window)
这是工作中最常踩的坑之一,比如把对象方法当作回调函数传递,很容易导致this指向跑偏。
场景3:new构造函数调用
用new关键字调用构造函数时,会创建一个新的对象实例,this直接指向这个新创建的实例。
function Person(name) {
this.name = name // this指向new出来的实例
this.sayName = function() {
console.log(this.name)
}
}
const person1 = new Person("张三")
person1.sayName() // 输出:张三
const person2 = new Person("李四")
person2.sayName() // 输出:李四
// 坑点:忘记写new,构造函数变成了普通函数
const person3 = Person("王五")
console.log(this.name) // 输出:王五(this指向window)
记住:nwe关键字的核心作用,就是改变this指向,让它指向新创建的实例。
场景4:显示绑定(call/apply/bind)
当我们想手动指定this指向时,用call、apply、bing这三个方法,优先级高于默认绑定和隐式绑定,是解决this指向的”万能钥匙“。
const cat = { name: "小猫" }
const dog = { name: "小狗" }
function sayLove(...args) {
console.log(`${this.name}喜欢吃${args.join("、")}`);
}
// call: 第一个参数是this指向,后面跟单个参数
sayLove.call(call,'小鱼干','罐头') // 输出: 小猫喜欢吃小鱼干、罐头 这时候函数里的this指向cat
// apply: 第一个参数是this的指向,后面跟参数数组
sayLove.apply(dog,["骨头", "肉"]) // 输出: 小狗喜欢吃骨头、肉,这时候函数里的this指向dog
// bind:返回一个新的函数,this固定指向,需要手动调用
const catLove = sayLove.bind(cat)
catLove("罐头") // 输出小猫喜欢吃罐头
💡区别:call和apply会立即执行函数,bind不会立即执行,适合需要延迟调用的场景。
二、特殊情况:箭头函数的this
箭头函数是ES6新增,它没有自己的this,this继承自外层的作用域(既定义时所处的环境),且一旦确定就无法改变(call/apply/bind也没用)。
const obj = {
name: '张三',
objThis: this,
// 普通方法
say1: function() {
console.log(this.name,'1')
setTimeout(function() {
console.log(this.name,'2')
},100)
},
// 箭头函数: this继承上下文的this,就是obj的this
say2: () => {
console.log(this === window,this === obj.objThis)
},
say3:function() {
setTimeout(() => {
console.log(this)
})
}
}
obj.say1() // 1.this指向obj 输出张三,2.this指向window
// 坑点:箭头函数的this是指向上下文的this,但是对象{}不算作用域,所以箭头函数的this指向window
obj.say2() // 输出true true
// 坑点: 因为say3是普通函数 obj.say3 调用 this指向obj,所以箭头函数的this指向obj,有些人会把第二种和第三种记混。
obj.say3() // 输出 obj对象
三. 一句话总结+面试避坑
判断this指向,按优先级排序:new绑定 > 显示绑定(call/apply/bind)> 隐式绑定(对象方法) > 默认绑定(普通调用);箭头函数特殊,继承外层this。
面试高频追问:为什么对象字面量中的箭头函数this指向全局? 答:对象字面量{}只是一个普通的对象定义,不算独立的词法作用域。而箭头函数的外层作用域是全局,因此this继承全局。