JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱

JavaScript this 指向详解:五种绑定规则、优先级与常见面试陷阱

在 JavaScript 中,this 是一个非常重要、也是前端面试经常考察的知识点。

很多人容易认为,this 指向函数所在的对象,但实际上并不完全正确。

普通函数的 this****主要由函数的调用方式决定,而不是函数定义的位置;箭头函数则使用定义时外层作用域的 this**。**

理解这一区别,就能掌握绝大多数 this 指向问题。

什么是 this?

this 是 JavaScript 提供的一个特殊关键字,用于在函数执行过程中访问当前的调用上下文。

例如:

javascript 复制代码
const user = {
  name: "Tom",

  sayHi() {
    console.log(this.name)
  }
}

user.sayHi() // Tom

这里通过 user.sayHi() 调用函数,因此 this 指向 user,相当于访问 user.name。

但如果改变函数的调用方式,即使是同一个函数,this 的指向也可能发生变化。

this 的五种指向规则

JavaScript 中常见的 this 绑定方式如下:

绑定方式 调用示例 this 指向
new 绑定 new Person() 新创建的实例
显式绑定 call/apply/bind 指定的对象
隐式绑定 obj.fn() 调用方法的对象
默认绑定 fn() 严格模式为 undefined,非严格模式通常为全局对象
箭头函数 () => {} 定义时外层作用域的 this

需要注意,前四种主要针对普通函数,而箭头函数没有自己的 this,因此不适用普通函数的绑定规则。

new 绑定

当使用 new 调用构造函数时,JavaScript 会创建一个新对象,并让函数执行时的 this 指向该对象。

javascript 复制代码
function Person(name) {
  this.name = name
}

const p = new Person("Tom")

console.log(p.name) // Tom

执行 new Person("Tom") 时,会创建实例对象 p,并通过 this.name = name 给它添加属性。

因此,new 绑定的 this 指向新创建的实例。

显式绑定:call、apply、bind

普通函数可以通过 call()、apply() 和 bind() 显式指定 this。

例如:

javascript 复制代码
function greet() {
  console.log(this.name)
}

const user = {
  name: "Tom"
}

greet.call(user) // Tom

这里的 greet.call(user) 表示执行 greet 时,让 this 指向 user。

三个方法的区别主要在于传参方式和执行时机。

方法 参数形式 是否立即执行
call() 逐个传入参数 是
apply() 使用数组传参 是
bind() 返回绑定 this 的新函数 否

例如:

javascript 复制代码
function greet(age, city) {
  console.log(this.name, age, city)
}

const user = { name: "Tom" }

// call:逐个传参
greet.call(user, 18, "北京")

// apply:数组传参
greet.apply(user, [18, "北京"])

// bind:返回新函数
const fn = greet.bind(user)
fn(18, "北京")

三种方式都可以输出:

复制代码
Tom 18 北京

其中 bind() 最重要的特点是不会立即执行函数,而是返回一个绑定了 this 的新函数。

隐式绑定:对象方法调用

当普通函数通过对象调用时,this 通常指向调用该方法的对象。

javascript 复制代码
const user = {
  name: "Tom",

  greet() {
    console.log(this.name)
  }
}

user.greet() // Tom

这里 greet() 是通过 user 调用的,所以 this 指向 user。

但函数的定义位置并不能决定 this。

例如:

javascript 复制代码
const user = {
  name: "Tom",

  greet() {
    console.log(this.name)
  }
}

const obj = {
  name: "Jack",
  greet: user.greet
}

obj.greet() // Jack

虽然 greet 最初定义在 user 中,但现在通过 obj.greet() 调用,所以 this 指向 obj。

这说明:普通函数的 this 取决于调用者,而不是函数最初属于哪个对象。

默认绑定:直接调用函数

如果普通函数没有通过对象、new 或显式绑定方式调用,就会使用默认绑定。

javascript 复制代码
function sayHi() {
  "use strict"
  console.log(this)
}

sayHi() // undefined

严格模式下,直接调用普通函数时,this 为 undefined。

非严格模式下,普通函数直接调用时,this 通常指向全局对象,例如浏览器普通脚本环境中的 window。

因此,默认绑定还需要结合严格模式和运行环境判断。

箭头函数:继承外层 this

箭头函数与普通函数最大的区别在于:

箭头函数没有自己的 this,它使用定义时外层作用域的 this。

例如:

javascript 复制代码
const user = {
  name: "Tom",

  greet() {
    const fn = () => {
      console.log(this.name)
    }

    fn()
  }
}

user.greet() // Tom

这里通过 user.greet() 调用普通函数,所以 greet 中的 this 指向 user。

而内部的箭头函数 fn 没有自己的 this,因此使用外层 greet 的 this,最终输出 "Tom"。

但是:

复制代码
const user = {
  name: "Tom",

  greet: () => {
    console.log(this.name)
  }
}

user.greet()

这里的箭头函数不会因为定义在 user 对象中,就让 this 指向 user。

它使用的是对象字面量外层作用域的 this,因此输出结果取决于具体环境。

所以,如果对象方法需要通过 this 访问对象本身,通常应该使用普通函数,而不是箭头函数。

this 的绑定优先级

对于普通函数,通常可以按照以下优先级理解:

复制代码
new 绑定
    ↓
显式绑定(call / apply / bind)
    ↓
隐式绑定(obj.fn())
    ↓
默认绑定(fn())

例如:

javascript 复制代码
function greet() {
  console.log(this.name)
}

const obj1 = {
  name: "Tom",
  greet
}

const obj2 = {
  name: "Jack"
}

obj1.greet()          // Tom
obj1.greet.call(obj2) // Jack

第二次调用虽然使用了 obj1.greet,但通过 call(obj2) 显式指定了 this,因此输出 "Jack"。

另外,bind() 绑定后的普通函数不能通过再次使用 call() 改变已绑定的 this,但通过 new 调用可构造的绑定函数时,new 绑定优先。

javascript 复制代码
function Person(name) {
  this.name = name
}

const obj = {}
const BoundPerson = Person.bind(obj)

const p = new BoundPerson("Tom")

console.log(p.name) // Tom
console.log(obj.name) // undefined

这里 new 创建了新的实例,因此 this 指向新实例,而不是 bind() 中指定的 obj。

常见陷阱:函数作为回调时 this 丢失

例如:

复制代码
const user = {
  name: "Tom",

  greet() {
    console.log(this.name)
  }
}

const fn = user.greet
fn()

这里不能认为 this 仍然指向 user。

因为:

复制代码
const fn = user.greet

只是把函数赋值给另一个变量,并没有将 user 一起绑定。

随后执行:

复制代码
fn()

属于直接调用,所以使用默认绑定。

同样的问题经常出现在定时器中:

复制代码
setTimeout(user.greet, 100)

传给 setTimeout 的是函数本身,不再是通过 user.greet() 调用,因此不能依赖它的 this 指向 user。

可以通过箭头函数解决:

复制代码
setTimeout(() => {
  user.greet()
}, 100)

或者使用 bind():

复制代码
setTimeout(user.greet.bind(user), 100)

两种方式都能让 greet 正确访问 user.name。

常见陷阱:嵌套函数不会自动继承 this

再看一个例子:

javascript 复制代码
const obj = {
  name: "Tom",

  outer() {
    console.log(this.name)

    function inner() {
      "use strict"
      console.log(this)
    }

    inner()
  }
}

obj.outer()

输出:

复制代码
Tom
undefined

因为 outer() 通过 obj 调用,所以 outer 的 this 指向 obj。

但内部的 inner() 是普通函数,并且直接调用,因此严格模式下 this 为 undefined。

如果希望内部函数使用外层的 this,可以改成箭头函数:

javascript 复制代码
const obj = {
  name: "Tom",

  outer() {
    const inner = () => {
      console.log(this.name)
    }

    inner()
  }
}

obj.outer() // Tom

由于箭头函数使用外层 outer 的 this,因此能够正确输出 "Tom"。

面试如何回答 this 的指向规则?

如果面试官问 JavaScript 中 this 的指向规则,可以这样回答:

JavaScript 普通函数的 this 主要由调用方式决定,常见绑定规则包括默认绑定、隐式绑定、显式绑定和 new 绑定。

直接调用普通函数通常使用默认绑定;通过对象调用时,this 指向调用对象;通过 call、apply、bind 可以显式指定 this;使用 new 调用构造函数时,this 指向新创建的实例。

其中,一般情况下绑定优先级为:

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

箭头函数比较特殊,它没有自己的 this,而是使用定义时外层作用域的 this,无法通过 call、apply 或 bind 改变,也不能用作构造函数。

总结

JavaScript 中 this 的核心并不是判断函数在哪里定义,而是判断函数怎样调用。

调用方式 示例 this 指向
new 绑定 new Person() 新实例
显式绑定 fn.call(obj) 指定对象
隐式绑定 obj.fn() 调用对象
默认绑定 fn() 取决于严格模式和运行环境
箭头函数 () => {} 外层作用域的 this

实际开发中,需要特别注意对象方法作为回调传递时可能丢失 this,以及普通嵌套函数不会自动继承外层函数的 this。

最后记住两句话:

普通函数:this 主要由调用方式决定。
箭头函数:没有自己的 this,使用定义时外层作用域的 this。

掌握这两条核心原则,再结合绑定优先级,就能解决大多数 JavaScript this 指向相关的面试题。

相关推荐
程序猿编码1 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
三天不学习2 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
xcs194052 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
bkspiderx3 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
程序员老陆3 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光3 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
码云数智-园园3 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
禾小西4 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨4 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言