JavaScript this关键字指向详解

在JavaScript编程中,this关键字是一个既核心又容易混淆的概念。它的指向并非固定不变,而是动态绑定的,取决于函数的调用方式、调用位置和上下文环境。理解this的指向规则,是掌握JavaScript面向对象编程、闭包、异步编程等高级特性的基础。

本文将从this的基本概念出发,结合丰富示例,详细分析不同场景下this的指向规则,并总结最佳实践,帮助你彻底掌握this的使用。

目录#

1. this的基本概念#

1.1 什么是this#

this是JavaScript的关键字,它指向当前函数的调用者(即函数执行时的上下文对象)。可以理解为:谁调用了函数,this就指向谁(需结合调用方式分析)。

this的作用是在函数内部引用外部上下文对象,避免硬编码对象名称,提高代码灵活性。示例:

const person = {
  name: "Alice",
  sayHi: function() {
    console.log(`Hi, I'm ${this.name}`); // this指向调用sayHi的对象(person)
  }
};
person.sayHi(); // 输出: Hi, I'm Alice

1.2 this的动态绑定特性#

与Java等语言中this固定指向类实例不同,JavaScript的this动态绑定的——其指向在函数调用时确定,而非定义时。同一个函数,调用方式不同,this指向也会不同。

示例:

function sayHi() {
  console.log(`Hi, I'm ${this.name}`);
}
 
const person1 = { name: "Bob", sayHi: sayHi };
const person2 = { name: "Charlie", sayHi: sayHi };
 
person1.sayHi(); // Hi, I'm Bob(this指向person1)
person2.sayHi(); // Hi, I'm Charlie(this指向person2)

2. 不同场景下的this指向#

2.1 全局环境中的this#

全局执行环境(非函数内部)中,this的指向取决于运行环境:

  • 浏览器this指向window对象。
  • Node.jsthis指向global对象(模块文件中,顶层this指向module.exports)。

示例(浏览器):

console.log(this === window); // true
var globalVar = "I'm global";
console.log(this.globalVar); // I'm global(window.globalVar等价于globalVar)

2.2 函数调用中的this#

函数调用方式决定this指向,核心规则如下:

2.2.1 普通函数调用(默认绑定)#

函数独立调用(无对象打点、无new/call/apply/bind)时,this遵循默认绑定

  • 非严格模式:this指向window(浏览器)或global(Node.js)。
  • 严格模式("use strict"):this指向undefined(防止意外修改全局对象)。

示例(非严格模式):

function foo() {
  console.log(this); // 浏览器中指向window
}
foo();
 
// 严格模式示例
function bar() {
  "use strict";
  console.log(this); // undefined
}
bar();

2.2.2 作为对象方法调用(隐式绑定)#

函数作为对象方法调用时(对象.方法()),this指向调用该方法的对象

示例:

const user = {
  name: "Eve",
  info: function() {
    console.log(`Name: ${this.name}`); // this指向user
  }
};
user.info(); // Name: Eve

陷阱:函数引用传递导致this丢失
若将对象方法赋值给变量后调用,函数会被独立调用,触发默认绑定:

const person = {
  name: "David",
  sayName: function() {
    console.log(this.name);
  }
};
 
const fn = person.sayName; // 方法赋值给变量,fn是独立函数
fn(); // 非严格模式输出空(window.name),严格模式报错

2.2.3 构造函数调用(new绑定)#

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

new的本质(简化):

  1. 创建空对象(newObj)。
  2. this指向newObj
  3. 执行构造函数(给this添加属性)。
  4. 返回新对象(构造函数无返回时)。

示例:

function Person(name) {
  this.name = name; // this指向新实例
  this.sayName = function() {
    console.log(this.name);
  };
}
 
const alice = new Person("Alice");
alice.sayName(); // Alice(this指向alice)

若构造函数返回对象,则new返回该对象而非this指向的实例:

function Person(name) {
  this.name = name;
  return { name: "Fake" }; // 返回对象
}
const bob = new Person("Bob");
console.log(bob.name); // Fake

2.2.4 call/apply/bind调用(显式绑定)#

JavaScript提供call/apply/bind,允许显式指定this指向

  • callfunc.call(thisArg, arg1, arg2, ...),立即调用函数。
  • applyfunc.apply(thisArg, [arg1, arg2, ...]),立即调用函数(参数为数组)。
  • bindfunc.bind(thisArg, arg1, ...),返回新函数(this永久绑定,可延迟调用)。

示例:

function sayInfo(job) {
  console.log(`Name: ${this.name}, Job: ${job}`);
}
 
const person = { name: "Frank" };
 
// call调用
sayInfo.call(person, "Engineer"); // Name: Frank, Job: Engineer
 
// apply调用
sayInfo.apply(person, ["Designer"]); // Name: Frank, Job: Designer
 
// bind调用(返回新函数)
const boundSayInfo = sayInfo.bind(person, "Teacher");
boundSayInfo(); // Name: Frank, Job: Teacher

bindthis永久绑定,无法通过call/apply修改:

boundSayInfo.call({ name: "Test" }, "Doctor"); // 仍输出Name: Frank, Job: Teacher

2.3 箭头函数中的this(词法作用域)#

箭头函数(() => {})的this词法作用域(静态绑定)的——指向定义时的外层作用域,与调用方式无关。

箭头函数没有自己的this,它捕获外层作用域的this

示例:

const person = {
  name: "Grace",
  sayHi: function() {
    // 普通函数回调:this指向window(非严格模式)
    setTimeout(function() {
      console.log(`Hi, I'm ${this.name}`); // 可能输出空
    }, 100);
 
    // 箭头函数回调:this捕获外层作用域(sayHi的this,即person)
    setTimeout(() => {
      console.log(`Hi, I'm ${this.name}`); // Hi, I'm Grace
    }, 200);
  }
};
 
person.sayHi();

箭头函数的this无法通过call/apply/bind修改(因为它没有自己的this):

const arrowFn = () => console.log(this);
arrowFn.call({ name: "Test" }); // 输出window(或global)

2.4 事件处理函数中的this#

DOM事件处理函数中,this的指向:

  • 普通函数:this指向触发事件的DOM元素
  • 箭头函数:this指向定义时的外层作用域(通常是window)。

示例:

<button id="btn">Click Me</button>
<script>
  const btn = document.getElementById("btn");
 
  // 普通函数:this指向btn
  btn.addEventListener("click", function() {
    console.log(this); // <button>元素
  });
 
  // 箭头函数:this指向window 
  btn.addEventListener("click", () => {
    console.log(this); // window
  });
</script>

若需在箭头函数中访问触发元素,可通过事件对象的target

btn.addEventListener("click", (e) => {
  console.log(e.target); // 触发事件的btn元素
});

2.5 回调函数中的this#

数组方法(如forEach/map)或异步回调(如setTimeout)中:

  • 普通函数回调:this指向全局对象(非严格模式)或undefined(严格模式),除非方法提供this参数(如forEach的第二个参数)。
  • 箭头函数回调:this指向定义时的外层作用域。

示例(数组方法):

const arr = [1, 2, 3];
const obj = { name: "ArrayObj" };
 
// 普通函数回调:this指向全局对象
arr.forEach(function() {
  console.log(this.name); // 可能输出空
});
 
// 普通函数回调,通过第二个参数指定this
arr.forEach(function() {
  console.log(this.name); // ArrayObj
}, obj);
 
// 箭头函数回调:this指向外层作用域(window)
arr.forEach(() => {
  console.log(this.name); // 可能输出空
});

3. 常见陷阱与问题#

3.1 函数作为回调传递时的this丢失#

将对象方法作为回调传递(如定时器、事件监听)时,this会丢失,指向全局对象或undefined

示例(定时器回调):

const user = {
  name: "Helen",
  sayName: function() {
    console.log(this.name);
  }
};
 
setTimeout(user.sayName, 1000); // 输出undefined(非严格模式下是window.name)

解决方法:

  • 使用箭头函数(捕获外层this):
    setTimeout(() => user.sayName(), 1000); // 输出Helen
  • 使用bind绑定this
    setTimeout(user.sayName.bind(user), 1000); // 输出Helen

3.2 多层嵌套函数的this指向混乱#

多层嵌套函数中,普通函数的this指向易混乱。

示例:

const outer = {
  name: "Outer",
  inner: function() {
    function nested() {
      console.log(this.name); // 独立调用,this指向全局对象
    }
    nested(); // 输出undefined(或空)
  }
};
 
outer.inner();

解决方法:

  • 使用箭头函数(捕获外层this):
    const outer = {
      name: "Outer",
      inner: function() {
        const nested = () => console.log(this.name); // this指向outer
        nested(); // 输出Outer
      }
    };
  • 保存this到变量(that/self):
    const outer = {
      name: "Outer",
      inner: function() {
        const that = this; // 保存outer的this
        function nested() {
          console.log(that.name); // 输出Outer
        }
        nested();
      }
    };

4. 最佳实践:确保this指向正确#

4.1 使用箭头函数固化this#

需要this指向外层作用域时(如回调、嵌套函数),优先使用箭头函数,避免this丢失。

示例(类的方法):

class User {
  constructor(name) {
    this.name = name;
    // 箭头函数方法,this始终指向User实例
    this.sayName = () => console.log(this.name);
  }
}
 
const jack = new User("Jack");
const fn = jack.sayName;
fn(); // Jack(this不会丢失)

4.2 使用bind显式绑定this#

需要将this永久绑定到某个对象时,使用bind

示例(回调函数绑定):

const obj = { name: "BindObj" };
function sayName() {
  console.log(this.name);
}
 
const boundFn = sayName.bind(obj);
boundFn(); // BindObj

4.3 保存this到变量(that/self模式)#

在函数内部,将this保存到变量(如that/self),在嵌套函数中使用该变量访问外层this

示例:

const user = {
  name: "Jack",
  sayName: function() {
    const that = this; // 保存this到that
    setTimeout(function() {
      console.log(that.name); // Jack
    }, 1000);
  }
};
 
user.sayName();

4.4 Class中使用箭头函数方法#

在ES6的class中,类的方法默认是普通函数,this易丢失。推荐使用箭头函数方法,确保this始终指向实例。

示例:

class User {
  constructor(name) {
    this.name = name;
    // 箭头函数方法,this始终指向User实例
    this.sayName = () => console.log(this.name);
  }
}
 
const jack = new User("Jack");
const fn = jack.sayName;
fn(); // Jack(this不会丢失)

5. 示例分析:不同场景下的this对比#

通过综合示例,对比不同调用方式下this的指向:

function showThis() {
  console.log("函数内部的this:", this);
  return this;
}
 
const obj1 = { name: "Obj1", showThis: showThis };
const obj2 = { name: "Obj2" };
 
// 1. 全局调用(默认绑定)
showThis(); // 非严格模式:window;严格模式:undefined
 
// 2. 作为对象方法调用(隐式绑定)
obj1.showThis(); // this指向obj1
 
// 3. call/apply调用(显式绑定)
showThis.call(obj2); // this指向obj2
showThis.apply(obj2); // this指向obj2
 
// 4. bind调用(显式绑定,永久)
const boundFn = showThis.bind(obj2);
boundFn(); // this指向obj2
boundFn.call(obj1); // 仍指向obj2(bind的this无法修改)
 
// 5. new调用(new绑定)
const newObj = new showThis(); // this指向新实例
 
// 6. 箭头函数对比
const arrowFn = () => {
  console.log("箭头函数的this:", this);
  return this;
};
 
arrowFn(); // this指向外层作用域(window)
arrowFn.call(obj2); // 仍指向window(箭头函数的this无法修改)

6. 总结#

JavaScript的this指向是动态绑定的,核心规则:

调用方式this指向优先级
普通函数调用全局对象(非严格模式)/undefined(严格模式)最低
对象方法调用调用方法的对象
call/apply/bind显式指定的对象中高
构造函数调用新创建的实例对象最高
箭头函数定义时的外层作用域-

7. 参考资料#