JavaScript this绑定规则:告别踩坑指南!

🧑‍💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣

前言

在JavaScript学习中,this绝对是"让人又爱又恨"的存在------它看似简单,用起来却总让人摸不着头脑,一不小心就踩坑。有人说它是"动态代词",有人说它是"隐式传递的对象引用",其实只要摸清它的绑定规则,就能轻松驾驭!今天就结合具体代码实例,从"为什么有this"到"怎么用this",一步步把this讲透~

一、先搞懂:为什么要有this?

很多新手会疑惑,明明可以直接通过对象名访问属性,为什么还要用this?其实this的核心作用,是提供一种更优雅的方式,隐式传递对象引用,让代码更简洁、更易于复用。

我们来看两组对比代码,一眼就能明白this的优势:

复制代码
// 没有this的写法,需要手动传递对象
function identify(context) {
  return context.name.toUpperCase()
}
function speek(context) {
  var greeting = 'hello, I am ' + identify(context)
  console.log(greeting); // 输出:hello, I am TOM
}
var me = { name: 'Tom' }
speek(me) // 每次调用都要手动传入me对象

// 有this的写法,无需手动传递,更简洁
function identify() {
  return this.name.toUpperCase() // this自动指向调用者
}
function speek() {
  var greeting = 'Hello, I am ' + identify.call(this)
  console.log(greeting); // 输出:Hello, I am TOM
}
var me = { name: 'Tom' }
speek.call(me) // 用call绑定this,无需重复传参

不难发现,有了this,我们不用每次调用函数都手动传递对象,尤其在复杂项目中,能大大简化代码,提升可读性和复用性

二、this用在哪?两大核心场景

this不是固定不变的,它的指向完全取决于"调用方式",而不是"定义位置"。它主要用在两个场景中,记住这两点,就能快速定位this的指向:

1. 全局作用域中的this

在全局作用域(不在任何函数内部)中,this直接指向window对象(浏览器环境下)。哪怕是单独的代码块,this依然指向window哦~

复制代码
{
  let a = this
  console.log(a); // 输出:Window 对象(浏览器环境)
}

// 全局作用域直接打印this
console.log(this === window); // 输出:true 

2. 函数作用域中的this

这是this最常用的场景,也是最容易踩坑的地方。函数中的this,指向调用该函数的对象,不同的调用方式,this的指向完全不同。接下来我们重点讲解函数中this的5种绑定规则,每一种都搭配你提供的代码实例,逐句解析~

三、this的5种绑定规则

规则1:默认绑定 → 独立调用的函数,this指向window

当函数被"独立调用"(没有任何上下文对象,直接调用)时,this默认绑定到window。哪怕函数嵌套在其他函数中,只要是独立调用,this依然指向window。

复制代码
// 实例1:直接调用函数
var a = 1
function foo() {
  console.log(this.a); // this指向window,window.a = 1
}
function bar () {
  var a = 2
  foo() // 独立调用foo,this不指向bar,依然指向window
}
bar() // 输出:1 ✅

// 实例2:嵌套函数独立调用
var a = 1
function bar () {
  var a = 2
  function foo() {
    console.log(this.a); // 独立调用,this指向window
  }
  foo() // 独立调用foo

bar() // 输出:1 ✅

// 实例3:无上下文调用函数
function foo() {
  var a = 1
  bar(this.a) // this指向window,window.a未定义,所以传递undefined
}
function bar(x) {
  console.log(x); // 输出:undefined ✅
}
foo() // 独立调用foo,this指向window

规则2:隐式绑定 → 上下文对象调用,this指向该对象

当函数被"上下文对象"调用时(比如:对象.函数()),this会隐式绑定到这个上下文对象上。简单说:谁调用函数,this就指向谁。

复制代码
// 实例1:对象直接调用函数
function foo() {
  console.log(this.a); // this指向调用者obj
}
var obj = {
  a: 1,
  foo: foo // foo作为obj的属性
}
obj.foo() // 调用者是obj,输出:1 

// 实例2:多层对象调用(this指向最近的调用者)
function foo() {
  console.log(this.a);
}
var obj = {
  a: 1,
  foo: foo
}
var obj2 = {
  a: 2,
  foo: obj // obj2的foo属性指向obj
}
obj2.foo.foo() // 最终调用foo的是obj,this指向obj,输出:1 

规则3:隐式丢失 → 函数引用被赋值,this指向"意外对象"

隐式丢失是最容易踩坑的情况!当函数的引用被赋值给另一个变量,或者作为参数传递时,原本的隐式绑定会失效,this会回归默认绑定(指向window)。

复制代码
// 实例:函数引用被赋值,隐式丢失
function foo() {
  console.log(this.a);
}
var obj = {
  a: 1,
  foo: foo
}
// 把obj.foo赋值给变量bar,此时bar是独立函数引用
var bar = obj.foo
var a = 2 // window.a = 2
bar() // 独立调用bar,this指向window,输出:2 (原本以为指向obj,实际丢失绑定)

简单总结:只要函数不是被"对象.函数()"直接调用,而是被赋值后调用,大概率会发生隐式丢失哦~

规则4:显式绑定 → 主动绑定this,精准控制指向

当我们想主动控制this的指向时,就可以用显式绑定------通过call、apply、bind三个方法,手动将this绑定到指定对象上。这三个方法的用法有细微区别,结合实例一次性搞懂:

复制代码
// 共同前提
var obj = { a: 1 }
function foo(x, y) {
  console.log(this.a, x + y); // this指向我们手动绑定的obj
}

// 1. call方法:直接调用函数,参数逐个传递
foo.call(obj, 1, 2) // 输出:1 3 ✅(call绑定this为obj,参数1、2逐个传入)

// 2. apply方法:直接调用函数,参数以数组形式传递
var arr = [1, 2]
foo.apply(obj, arr) // 输出:1 3 ✅(apply参数是数组,会自动解构)

// 3. bind方法:不直接调用函数,返回一个"绑定了this"的新函数
const bar = foo.bind(obj, 2, 4) // 绑定this为obj,预设参数2、4
bar(3) // 调用新函数,额外传入参数3,最终x=2,y=4+3=7?不!注意:bind预设参数在前
// 正确解析:bind预设的2、4是前两个参数,bar(3)是第三个参数?不,foo只有x、y两个参数
// 最终x=2,y=4,输出:1 6 ✅(bind绑定后,参数会固定,后续传入的参数会忽略多余的)

小技巧:call和apply的区别只在参数传递方式,bind和前两者的区别是"不立即调用",适合需要延迟调用的场景(比如定时器、事件绑定)。

规则5:new绑定 → 构造函数中,this指向实例对象

当用new关键字调用构造函数时,this会绑定到"新创建的实例对象"上。这也是构造函数能创建多个实例的核心原因,我们结合你提供的代码,解析new的底层逻辑和特殊情况:

复制代码
// 实例1:正常new调用,this指向实例
function Animal() {
  this.name = '米奇' // this指向new创建的实例p
}
let a = new Animal()
let a2 = new Animal()
console.log(a.name); // 输出:米奇 ✅(a是实例,this指向a)
console.log(a2.name); // 输出:米奇 ✅(a2是另一个实例,this指向a2)

// 实例2:特殊情况------构造函数return引用类型,new绑定失效(如果renturn 原始对象,对new的绑定不影响)
function Animal() {
  this.name = '米奇'
  return 123 // return的是原始类型
}
let a = new Animal()
console.log(a); // 输出:Animal { name: '米奇' } ✅
console.log(a.name); // 输出:米奇 ✅


function Animal() {
  this.name = '米奇'
  return {b: 1} // return的是引用类型(对象)
}
let a2 = new Animal()
console.log(a2); // 输出:{b: 1} ✅(new绑定失效,返回return的对象)
console.log(a2.name); // 输出:undefined ✅(a2不再是Animal实例,没有name属性)

补充new的底层原理(对应你代码中的注释),帮你彻底理解:

创建一个空对象(var obj = {});

将构造函数的this绑定到这个空对象(Animal.call(obj));

给空对象添加属性(obj.name = '米奇');

将空对象的原型指向构造函数的原型(obj.proto = Animal.prototype);

如果构造函数没有return,或者return的是基本类型,就返回这个空对象(实例);如果return的是引用类型,就返回这个引用类型。

四、特殊情况:箭头函数中的this

箭头函数是ES6新增的语法,它和普通函数最大的区别的是:箭头函数没有自己的this!箭头函数中的this,永远指向它"外层最近的非箭头函数"的this,而且一旦绑定,就无法修改(call、apply、bind也没用)。

复制代码
function foo() {
  var bar = () => {
    this.a = 2 // 箭头函数没有this,指向外层非箭头函数foo的this
  }
  bar()
}
var obj = {
  a: 1,
  baz: foo
}
obj.baz() // 调用foo,foo的this指向obj(隐式绑定)
console.log(obj); // 输出:{a: 2, baz: ƒ} ✅(箭头函数的this指向obj,修改了obj.a)

// 验证:箭头函数this无法修改
var obj2 = {a: 3}
bar.call(obj2) // 试图用call修改this,无效
console.log(obj.a); // 依然是2 ✅
console.log(obj2.a); // 还是3 ✅

小提醒:箭头函数适合用在回调函数中(比如定时器、数组方法),可以避免this指向混乱;但不适合用在构造函数中,因为它没有自己的this,无法创建实例哦~

五、总结:this指向判断口诀

看完上面的规则,可能会觉得有点多,教大家一个简单的判断口诀,遇到this就按这个顺序来,永远不会错:

1.看函数是不是用new调用?是 → this指向实例;

2.看函数是不是用call、apply、bind调用?是 → this指向绑定的对象;

3.看函数是不是被上下文对象调用(对象.函数())?是 → this指向该对象;

4.看函数是不是箭头函数?是 → this指向外层最近的非箭头函数的this;

5.以上都不是 → 默认绑定,this指向window(严格模式下是undefined)。

其实this并没有那么难,核心就是"谁调用,指向谁",再记住箭头函数和new的特殊情况,多练几个实例,就能轻松掌握。希望这篇文章能帮你吃透this,从此告别this踩坑烦恼,在JavaScript的路上越走越顺哦~

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

相关推荐
小陈同学呦7 小时前
前端如何处理订单状态导航的数据竞态问题
前端·javascript
开发者每周简报7 小时前
网海三部曲·无名宗师传
javascript·人工智能
isyangli_blog7 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008117 小时前
FastAPI APIRouter
开发语言·python
Benszen7 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆7 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木7 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充8 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~8 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6168 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang