JavaScript this 指向完全指南:调用位置决定一切

1. 引言

在 JavaScript 中,this 的指向问题一直是初学者乃至资深开发者最容易混淆的知识点之一。很多人在面试或实际开发中,面对 this 的指向时常常一头雾水。其实,理解 this 的关键只有一句话:this 的指向取决于函数的调用位置(调用方式),而不是定义位置。

本文将系统性地梳理 JavaScript 中 this 的五大绑定规则:默认绑定、隐式绑定、显式绑定、new 绑定,以及箭头函数的特殊行为,并通过大量可运行的代码示例,帮助你彻底掌握 this 的指向规律。

2. 核心概念:调用位置决定一切

在深入各种绑定规则之前,我们首先要建立一个核心认知:this 是在函数被调用时确定的,它指向谁,取决于这个函数是被谁、以什么方式调用的。

javascript 复制代码
function showThis() {
  console.log(this);
}

// 直接调用:this 指向全局对象(浏览器中为 window)
showThis(); // window

// 作为对象方法调用:this 指向该对象
const obj = {
  name: 'obj',
  show: showThis
};
obj.show(); // obj

同一个函数,仅仅因为调用方式不同,this 的指向就完全不同。这就是「调用位置决定一切」的含义。

3. 默认绑定

默认绑定是最简单的一种情况:当函数独立调用 (不带任何修饰)时,this 指向全局对象。

javascript 复制代码
function foo() {
  console.log(this);
}

foo(); // 浏览器中输出 window,Node.js 中输出 global

在严格模式下,默认绑定的 this 会是 undefined:

javascript 复制代码
'use strict';

function foo() {
  console.log(this);
}

foo(); // undefined

注意 :在 ES6 模块中,代码默认处于严格模式,因此独立调用函数时 this 为 undefined。

4. 隐式绑定

当函数作为某个对象的方法 被调用时,this 隐式地指向该对象。这是最常见的绑定方式。

javascript 复制代码
const person = {
  name: 'Alice',
  greet() {
    console.log(`Hello, I am ${this.name}`);
  }
};

person.greet(); // Hello, I am Alice

4.1 隐式丢失

隐式绑定有一个常见的陷阱:隐式丢失 。当方法被赋值给另一个变量再调用时,this 会丢失原来的绑定,退化为默认绑定。

javascript 复制代码
const person = {
  name: 'Alice',
  greet() {
    console.log(`Hello, I am ${this.name}`);
  }
};

const greet = person.greet;
greet(); // Hello, I am undefined(this 指向全局对象)

4.2 回调函数中的隐式丢失

将方法作为回调传入时,同样会发生隐式丢失:

javascript 复制代码
const person = {
  name: 'Alice',
  greet() {
    console.log(`Hello, I am ${this.name}`);
  }
};

setTimeout(person.greet, 1000); // Hello, I am undefined

5. 显式绑定

显式绑定是通过 call、apply、bind 三个方法,强制指定 函数执行时的 this 指向。

5.1 call 与 apply

call 和 apply 都会立即执行函数,区别仅在于传参方式:call 逐个传参,apply 以数组传参。

javascript 复制代码
function introduce(city, age) {
  console.log(`${this.name} lives in ${city}, age ${age}`);
}

const person = { name: 'Bob' };

introduce.call(person, 'Beijing', 25);  // Bob lives in Beijing, age 25
introduce.apply(person, ['Shanghai', 30]); // Bob lives in Shanghai, age 30

5.2 bind

bind 不会立即执行函数,而是返回一个绑定了 this 的新函数,之后可以随时调用。

javascript 复制代码
function introduce(city) {
  console.log(`${this.name} lives in ${city}`);
}

const person = { name: 'Carol' };
const boundIntroduce = introduce.bind(person);

boundIntroduce('Guangzhou'); // Carol lives in Guangzhou

5.3 显式绑定解决隐式丢失

利用 bind,我们可以修复前面回调函数中的隐式丢失问题:

javascript 复制代码
const person = {
  name: 'Alice',
  greet() {
    console.log(`Hello, I am ${this.name}`);
  }
};

setTimeout(person.greet.bind(person), 1000); // Hello, I am Alice

6. new 绑定

使用 new 关键字调用函数(此时函数作为构造函数)时,this 会指向新创建的对象实例。

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

const alice = new Person('Alice', 25);
console.log(alice.name); // Alice
console.log(alice.age);  // 25

new 绑定做了四件事:

  1. 创建一个全新的空对象;
  2. 将这个空对象的原型指向构造函数的 prototype;
  3. 将 this 绑定到这个新对象上;
  4. 如果构造函数返回了对象,则返回该对象;否则返回新创建的对象。
javascript 复制代码
function Person(name) {
  this.name = name;
  // 没有显式 return,默认返回 this 指向的新对象
}

const bob = new Person('Bob');
console.log(bob.name); // Bob

7. 箭头函数与优先级

箭头函数是 ES6 引入的特殊函数,它不绑定自己的 this ,而是继承外层作用域的 this。箭头函数的 this 在定义时就确定了,与调用方式无关。

javascript 复制代码
const person = {
  name: 'Alice',
  greet: () => {
    console.log(`Hello, I am ${this.name}`);
  }
};

person.greet(); // Hello, I am undefined(this 继承自外层作用域,即全局)

7.1 箭头函数解决回调中的 this 问题

箭头函数非常适合用于回调场景,因为它不会改变 this 的指向:

javascript 复制代码
const person = {
  name: 'Alice',
  greet() {
    setTimeout(() => {
      console.log(`Hello, I am ${this.name}`);
    }, 1000);
  }
};

person.greet(); // Hello, I am Alice(箭头函数继承了 greet 的 this)

7.2 箭头函数不能作为构造函数

箭头函数没有自己的 this,因此不能使用 new 调用:

javascript 复制代码
const Person = (name) => {
  this.name = name;
};

// 报错:Person is not a constructor
// const p = new Person('Alice');

7.3 箭头函数无法被显式绑定

由于箭头函数不绑定 this,call、apply、bind 对它无效:

javascript 复制代码
const arrow = () => {
  console.log(this);
};

const obj = { name: 'obj' };
arrow.call(obj); // 仍然输出全局对象,而不是 obj

8. 绑定优先级总结

当多种绑定规则同时出现时,优先级从高到低为:

  1. new 绑定(最高)
  2. 显式绑定 (call / apply / bind)
  3. 隐式绑定(对象方法调用)
  4. 默认绑定(独立调用,最低)
javascript 复制代码
function foo() {
  console.log(this.name);
}

const obj1 = { name: 'obj1', foo };
const obj2 = { name: 'obj2', foo };

// 隐式绑定
obj1.foo(); // obj1

// 显式绑定优先级高于隐式绑定
obj1.foo.call(obj2); // obj2

// new 绑定优先级最高
const bound = foo.bind(obj1);
new bound(); // undefined(new 绑定覆盖了 bind 的 this)

8.1 箭头函数不参与优先级

箭头函数不遵循上述优先级规则,它的 this 完全由定义时的外层作用域决定,任何绑定方式都无法改变。

9. 实战案例与常见陷阱

9.1 事件处理中的 this

在 DOM 事件处理中,this 默认指向触发事件的元素:

javascript 复制代码
const button = document.querySelector('#btn');

button.addEventListener('click', function() {
  console.log(this); // button 元素
});

// 使用箭头函数时,this 指向外层作用域
button.addEventListener('click', () => {
  console.log(this); // window(或外层作用域)
});

9.2 对象方法中的嵌套函数

javascript 复制代码
const obj = {
  name: 'obj',
  outer() {
    function inner() {
      console.log(this); // 独立调用,this 为全局对象
    }
    inner();
  }
};

obj.outer(); // window

解决方案:使用箭头函数或保存 this:

javascript 复制代码
const obj = {
  name: 'obj',
  outer() {
    const inner = () => {
      console.log(this); // 继承 outer 的 this,指向 obj
    };
    inner();
  }
};

obj.outer(); // obj

9.3 解构赋值中的 this 丢失

javascript 复制代码
const person = {
  name: 'Alice',
  greet() {
    console.log(`Hello, I am ${this.name}`);
  }
};

const { greet } = person;
greet(); // Hello, I am undefined(隐式丢失)

10. 总结与速查表

绑定方式 触发条件 this 指向
默认绑定 独立函数调用 全局对象(严格模式下为 undefined)
隐式绑定 作为对象方法调用 调用该方法的对象
显式绑定 call / apply / bind 手动指定的对象
new 绑定 new 调用构造函数 新创建的实例对象
箭头函数 定义时确定 外层作用域的 this

核心记忆口诀:

  • 独立调用看全局;
  • 方法调用看对象;
  • call / apply / bind 手动指定;
  • new 创建新实例;
  • 箭头函数看外层,绑定规则全失效。

掌握 this 的指向规律,是深入理解 JavaScript 作用域、闭包、原型链等高级特性的基础。希望本文能帮助你彻底告别 this 指向的困惑。

相关推荐
denlaku1 小时前
JDK 13 新特性详解
java·开发语言
念越1 小时前
题库、练习与错题本代码阅读
开发语言·数据结构
WeiXin_DZbishe1 小时前
基于Node.js的马拉松赛事管理系统的设计与实现-计算机毕设 附源码【课程设计】90211
java·javascript·vue.js·python·php·课程设计
l1t1 小时前
几种数据库软件count(distinct)和count()from group by子查询性能测试
开发语言·数据库
熊猫钓鱼>_>1 小时前
大学图论第 6 讲:网络流与二分图匹配——对偶性让瓶颈自己现形
开发语言·算法·面试·职场和发展·图论·校招·面经
方便面不加香菜1 小时前
C++ 关于多态的知识点讲解
开发语言·c++
西西学代码1 小时前
像素和颜色之间的转换(Flutter)
前端·javascript·flutter
jimy12 小时前
C++ 类成员函数的内联定义(inline)详解
开发语言·c++
Nebula_g2 小时前
JavaSE高级:动态代理
java·开发语言·算法·javase