Skip to content

核心原因:调用方式决定 this

javascript
class User {
  constructor(name) {
    this.name = name;
  }
  
  sayHi() {
    console.log(`你好,我是 ${this.name}`);
  }
}

const user = new User('Alice');

// 1. 作为方法调用:this 指向实例
user.sayHi(); // "你好,我是 Alice" ✓

// 2. 赋值给变量后调用:this 丢失
const greet = user.sayHi;
greet(); // "你好,我是 undefined" ✗ (或报错)

为什么? JS 引擎在运行时根据**调用点(call site)**确定 this

调用方式this 指向示例
obj.method()obj 对象user.sayHi()
method()全局对象/undefinedconst m = obj.method; m()
call/apply(bind)显式指定的对象sayHi.call(user)
回调函数通常丢失/被重新指定setTimeout(obj.method, 100)

3 种常见场景与表现

场景 1:作为对象方法调用(正常)

javascript
user.sayHi(); // this === user

原理:点号 . 或方括号 [] 触发"方法调用模式",自动将左侧对象作为 this


场景 2:提取后单独调用(this 丢失)

javascript
const greet = user.sayHi; // 只复制了函数本身,没复制"绑定关系"
greet(); // 独立调用,this === window/global 或 undefined (严格模式)

原理:函数在 JavaScript 中是一等公民,可以独立传递。赋值操作丢失了与原始对象的关联。

陷阱高发区

javascript
// 回调函数
setTimeout(user.sayHi, 1000); // this 丢失

// 事件监听
button.addEventListener('click', user.sayHi); // this 指向 button 元素

// 数组方法
['Bob', 'Carol'].forEach(user.sayHi); // this 丢失

场景 3:箭头函数(词法作用域 this)

javascript
class Timer {
  constructor() {
    this.seconds = 0;
  }
  
  // 传统方法:this 容易丢失
  tick() {
    setInterval(function() {
      this.seconds++; // ❌ this 指向全局对象
    }, 1000);
  }
  
  // 箭头函数:this 从定义处继承
  tickArrow() {
    setInterval(() => {
      this.seconds++; // ✅ this 指向 Timer 实例
    }, 1000);
  }
}

原理:箭头函数没有自己的 this,它静态地继承外层作用域的 this(定义时确定,不可改变)。


解决方案

方案 1:类字段语法(推荐)

javascript
class User {
  name = 'Alice';
  
  // 箭头函数自动绑定到实例
  sayHi = () => {
    console.log(this.name); // 永远指向实例
  }
}

const greet = new User().sayHi;
greet(); // ✅ "Alice"

方案 2:构造函数中手动绑定

javascript
class User {
  constructor(name) {
    this.name = name;
    this.sayHi = this.sayHi.bind(this); // 显式绑定
  }
  
  sayHi() {
    console.log(this.name);
  }
}

方案 3:调用时使用 bind()

javascript
const greet = user.sayHi.bind(user); // 创建永久绑定的新函数
setTimeout(greet, 100); // ✅ this 正确

总结

特性普通函数箭头函数
this 来源运行时的调用方式定义时的外层作用域
是否可变✅ 可通过 call/apply/bind 改变❌ 永远固定
适用场景需要动态 this(如事件处理)需要保留外层 this(如回调)

记住这个口诀: **this 不看定义看调用,箭头函数是例外** 。理解这点,就能掌握 90% 的 this` 问题。

箭头函数对this的处理

箭头函数是 ES6(ECMAScript 2015) 才引入的语法糖,距今约9年。它并非底层新机制,而是对现有作用域和闭包规则的封装。

实现核心:词法作用域捕获

箭头函数的本质是在定义时捕获外层的 this,并把它当作普通变量处理,就像函数内部的闭包变量一样。


编译器视角:Babel 如何转译

箭头函数会被编译成普通函数 + 临时变量保存 this

原始代码(ES6)

javascript
class Timer {
  constructor() {
    this.seconds = 0;
  }
  
  start() {
    setInterval(() => {
      this.seconds++; // 箭头函数
    }, 1000);
  }
}

Babel 转译后(ES5)

javascript
var Timer = function() {
  var _this = this; // 关键:用一个变量捕获 this
  
  this.seconds = 0;
  
  this.start = function() {
    setInterval(function() {
      _this.seconds++; // 使用外层捕获的 _this
    }, 1000);
  };
};

关键点

  1. 构造函数或外层函数中定义 _this = this
  2. 箭头函数内的 this 直接替换为 _this
  3. _this 通过闭包机制一直存在于内层函数作用域

为什么不能 new 箭头函数?

javascript
const Foo = () => {};
new Foo(); // ❌ TypeError: Foo is not a constructor

底层原因:普通函数有 [[Construct]] 内部方法(用于 new),而箭头函数没有这个方法。引擎在创建箭头函数时,故意省略了构造相关内部槽位:

特性普通函数箭头函数
[[Construct]]✅ 有❌ 无
prototype 属性✅ 有(指向原型对象)❌ 无
new✅ 可以❌ 不可以

这让它比普通函数更轻量,也避免了 this 绑定的复杂性。


它真的“没有 this”吗?

技术上不准确。箭头函数this 引用,但不进行单独的绑定解析,而是直接沿用外层作用域的 this 值。引擎实现上类似:

javascript
// 伪代码表示引擎处理
function createArrowFunction(outerThis) {
  return function(...args) {
    // 忽略调用时的 this,强制使用 outerThis
    return originalFunction.call(outerThis, ...args);
  };
}

小结

  1. 时间:ES6 (2015) 引入,确实是"后来的"
  2. 实现语法糖 + 编译时转换,本质是闭包捕获
  3. 性能:比普通函数略快(少了一次动态 this 查找)
  4. 设计目标:解决回调地狱中的 this 丢失问题,让代码更简洁

理解箭头函数的关键是:它并非魔法,只是让编译器帮你写了 _this = this 这行代码