为什么总写错 this?原来是函数有“二心”

一、引言:为什么需要箭头函数?

JavaScript 是一门充满灵活性的语言,但也因此带来了许多“历史遗留问题”。其中之一,便是函数(function)在不同上下文中的“二义性”,它指的是 ——

JavaScript 的函数既是行为的抽象(执行逻辑),又是作用域(上下文)的构造器。

换句话说,普通函数中的 this 并不是语法层面“确定”的变量,而是运行时“动态绑定”的对象,这给代码的可维护性、可预测性带来了不少隐患。 

举个🌰,一个函数有如下两个用法:

Number();
new Number();

Date();
new Date();

而这个问题,直到 ES6 得以解决。

1、class(语法糖)本质仍是函数,规范构造器使用方式。

function Person(name) {
  this.name = name;
}
const p = new Person('Alice'); // 可行
Person('Bob'); // 容易出错:this 指向 window

class PersonClass {
  constructor(name) {
    this.name = name;
  }
}
const p2 = new PersonClass('Carol'); // 可行
PersonClass('Dave'); // TypeError: Class constructor cannot be invoked without 'new'

2、箭头函数,用来表示一个简单的指令序列,不含有构造器内部属性。

二、“函数的二义性”到底是什么?

在 JavaScript 中,函数是一等公民,可以传递、赋值、作为回调等等。而最麻烦的是它的 this 指向是动态的

function sayHello() {
  console.log(this.name);
}

const person = {
  name: 'Alice',
  sayHello,
};

person.sayHello(); // Alice
sayHello(); // undefined 或 window.name

函数内部的 this 到底指向什么?要看它是如何被调用的,这导致函数具有“二义性”:

  • 作为函数调用时:this 指向 window(严格模式下是 undefined)
  • 作为对象方法调用时:this 指向该对象
  • 作为回调函数传递时:this 又可能变化
这会带来什么问题呢?

回调中的 this 丢失

function Timer() {
  console.log('外', this); // 外 Timer {seconds: 0}
  this.seconds = 0;
  setTimeout(function () {
    this.seconds++; // this 不再指向 Timer 实例
    console.log('内', this, this.seconds); // 内 Timeout {} NaN
  }, 1000);
}

new Timer();

输出将是:NaN,内部 this 执行 Timeout 对象或者 window 对象。

这就暴露出函数作为“行为”时的作用域污染问题。我们必须用各种 hack 方法:

function Timer() {
  this.seconds = 0;
  const self = this;
  setTimeout(function () {
    self.seconds++; // 通过闭包保存 this
    console.log('内', self, self.seconds); // 内 Timer { seconds: 1 } 1
  }, 1000);
}

new Timer();

或者使用箭头函数

function Timer() {
  this.seconds = 0;
  setTimeout(() => {
    this.seconds++; // 箭头函数没有 this,直接捕获外部上下文
    console.log('内', this, this.seconds); // 内 Timer { seconds: 1 } 1
  }, 1000);
}

new Timer();

三、箭头函数的底层原理是什么?

1. 箭头函数没有自己的 this

箭头函数的最核心特性就是:它不绑定自己的 this,而是从声明时的上下文“捕获” this,这叫做 词法作用域绑定(lexical scoping)。

const person = {
  name: 'Bob',
  greet: function () {
    setTimeout(() => {
      console.log(this.name); // Bob, this 指向 person
    }, 1000);
  },
};

person.greet();
2. 箭头函数也没有 arguments、super 或 new.target

箭头函数是轻量级函数,它不适合用来构造对象:

const Fn = () => {};
const obj = new Fn(); // TypeError: Fn is not a constructor

箭头函数适合用于 表达行为,而不是 构建类型或上下文

四、为什么需要箭头函数?

背后的设计哲学:语义单一、作用域清晰

函数既可以作为逻辑体,也可以承载上下文语义(this)—— 这是 JavaScript 的“原罪”。

箭头函数试图通过这样的原则来解决这个问题:

一个函数,若不需要 this、super、arguments 等上下文语义,就不应承载这些职责。

箭头函数就此诞生,满足了以下设计目标:

特性传统函数箭头函数
this动态绑定词法绑定
用作构造函数支持不支持
arguments没有
super没有

总结:箭头函数被设计为“纯粹表达逻辑”的语法糖,而不是“上下文承载者”。

为什么叫“箭头函数”?

箭头函数的语法:

const fn = (x) => x * 2;

用简洁的 “=>” 语法代替了传统的 function 关键字,因此得名。

它的简洁形式和数学函数表示法极为接近,比如:

f(x) = x * 2  // 类似数学表达
详细对比场景:箭头函数 vs 普通函数

1、回调函数

const obj = {
  name: 'Tom',
  list: [1, 2, 3],
  print() {
    this.list.forEach(function (item) {
      console.log(this.name); // undefined,因为 this 不再指向 obj
    });
    this.list.forEach((item) => {
      console.log(this.name); // Tom,因为箭头函数没有自己的 this,继承自 print 方法的上下文
    });
  },
};

obj.print();
  • function(item) {} 是普通函数,它的 this 在运行时动态绑定,这里是 window(非严格模式)或 undefined(严格模式)。
  • => 箭头函数没有自己的 this,它从定义时的上下文中“继承”了 this,因此始终指向 obj。

2、对象方法

const obj = {
  name: 'Tom',
  list: [1, 2, 3],
  print1: () => {
    console.log(this); // {}
  },
  print2() {
    console.log(this); // { name: 'Tom', list: [ 1, 2, 3 ], print1: [Function: print1], print2: [Function: print2] }
  },
};

obj.print1();
obj.print2();

原因:箭头函数不会绑定 this,因此它的 this 指向的是全局对象,而不是 obj。

3、使用场景

什么时候使用箭头函数?

  • 回调函数(如 map、forEach、filter)
  • 定时器、Promise、异步流控制
  • Vue/React 中保持 this 指向
  • 函数体简洁、无副作用的函数逻辑

什么时候使用普通函数?

  • 构造函数(function Person() {})
  • 原型链上的方法定义
  • 使用 this 表示事件绑定目标(如 addEventListener)
  • 需要访问 arguments 对象的情况
未来趋势:函数语义“解耦化”
  • 箭头函数越来越成为默认写法

  • 传统函数用于特殊场景(构造函数、对象方法)

  • 语言设计也逐渐在往 “职责单一” 演进

总结:箭头函数并不是用来“取代”普通函数的,而是为了解决原有语法中由于 this 机制不一致而带来的逻辑混乱问题,同时带来语法优化、结构清晰等额外好处。因此,箭头函数是函数语义演化的必然。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值