普通函数与箭头函数的区别

0 阅读6分钟

在前端面试中,“箭头函数与普通函数的区别”是一道出现频率极高的基础题。然而,很多开发者仅停留在“写法更简单”或“this 指向不同”的浅层认知上。作为一名合格的前端工程师,我们需要从 JavaScript 引擎的执行机制层面,深入理解这两者的本质差异。

本文将从语法特性、运行原理、核心差异及面试实战四个维度,对这一知识点进行全方位的拆解。

第一部分:直观的代码对比(语法层)

首先,我们通过代码直观地感受两者在书写层面上的差异。箭头函数(Arrow Function)本质上是 ES6 引入的一种语法糖,旨在简化函数定义。

1. 基础写法对比

JavaScript

// 普通函数声明
function add(a, b) {
    return a + b;
}

// 普通函数表达式
const sub = function(a, b) {
    return a - b;
};

// 箭头函数
const mul = (a, b) => {
    return a * b;
};

2. 箭头函数的语法糖特性

箭头函数支持高度简化的写法,但同时也引入了一些特定的语法规则:

  • 省略参数括号:当且仅当只有一个参数时,可以省略括号。
  • 隐式返回:当函数体只有一行语句时,可以省略花括号 {} 和 return 关键字。

JavaScript

// 省略参数括号
const square = x => x * x;

// 完整写法对比
const squareFull = (x) => {
    return x * x;
};

3. 返回对象字面量的陷阱

这是初学者最容易踩的坑。当隐式返回一个对象字面量时,必须使用小括号 () 包裹,否则 JS 引擎会将对象的花括号 {} 解析为函数体的代码块。

JavaScript

// 错误写法:返回 undefined,引擎认为 {} 是代码块
const getUserError = id => { id: id, name: 'User' };

// 正确写法:使用 () 包裹
const getUser = id => ({ id: id, name: 'User' });

第二部分:特性分析(原理层)

在深入差异之前,我们需要界定两种函数的底层特性,这是理解它们行为差异的基石。

普通函数(Regular Function)

  • 动态作用域机制:this 的指向在函数被调用时决定,而非定义时。
  • 完整性:拥有 prototype 属性,可以作为构造函数。
  • 参数集合:函数体内自动生成 arguments 类数组对象。
  • 构造能力:具备 [[Construct]] 内部方法和 [[Call]] 内部方法。

箭头函数(Arrow Function)

  • 词法作用域机制:this 的指向在函数定义时决定,捕获外层上下文。
  • 轻量化:设计之初就是为了更轻量级的执行。没有 prototype 属性,没有 arguments 对象。
  • 非构造器:只有 [[Call]] 内部方法,没有 [[Construct]] 内部方法,因此不可实例化。

第三部分:核心差异深度解析

接下来,我们将从底层机制出发,分点剖析两者的核心差异。

1. this 指向机制(核心差异)

这是两者最根本的区别。

  • 普通函数:this 指向取决于调用位置

    • 默认绑定:独立调用指向 window(严格模式下为 undefined)。
    • 隐式绑定:作为对象方法调用,指向该对象。
    • 显式绑定:通过 call、apply、bind 修改指向。
  • 箭头函数:this 遵循词法作用域。它没有自己的 this,而是捕获定义时所在外层上下文的 this。一旦绑定,无法被修改

场景演示:setTimeout 中的回调

JavaScript

const obj = {
    name: 'Juejin',
    // 普通函数
    sayWithRegular: function() {
        setTimeout(function() {
            console.log('Regular:', this.name);
        }, 100);
    },
    // 箭头函数
    sayWithArrow: function() {
        setTimeout(() => {
            console.log('Arrow:', this.name);
        }, 100);
    }
};

obj.sayWithRegular(); // 输出: Regular: undefined (或 window.name)
obj.sayWithArrow();   // 输出: Arrow: Juejin

解析

  • sayWithRegular 中的回调函数是独立调用的,this 指向全局对象(浏览器中为 window),通常没有 name 属性。
  • sayWithArrow 中的箭头函数在定义时,捕获了外层 sayWithArrow 函数的 this(即 obj),因此能正确访问 name。即便 setTimeout 是在全局环境中执行回调,箭头函数的 this 依然保持不变。

显式绑定无效验证

JavaScript

const arrow = () => console.log(this);
const obj = { id: 1 };

// 尝试修改箭头函数的 this
arrow.call(obj); // 依然输出 window/global

2. 构造函数能力

由于箭头函数内部缺失 [[Construct]] 方法和 prototype 属性,它不能被用作构造函数。

JavaScript

const RegularFunc = function() {};
const ArrowFunc = () => {};

console.log(RegularFunc.prototype); // { constructor: ... }
console.log(ArrowFunc.prototype);   // undefined

new RegularFunc(); // 正常执行
new ArrowFunc();   // Uncaught TypeError: ArrowFunc is not a constructor

这一特性说明箭头函数旨在处理逻辑运算和回调,而非对象建模。

3. 参数处理(arguments vs Rest)

在普通函数中,我们习惯使用 arguments 对象来获取不定参数。但在箭头函数中,访问 arguments 会导致引用错误(ReferenceError),因为它根本不存在。

正确方案:ES6 推荐使用 剩余参数(Rest Parameters)

JavaScript

// 普通函数
function sumRegular() {
    return Array.from(arguments).reduce((a, b) => a + b);
}

// 箭头函数:使用 ...args
const sumArrow = (...args) => {
    // console.log(arguments); // 报错:arguments is not defined
    return args.reduce((a, b) => a + b);
};

console.log(sumArrow(1, 2, 3)); // 6

4. 方法定义中的陷阱

鉴于箭头函数的 this 绑定机制,不推荐在定义对象原型方法或对象字面量方法时使用箭头函数。

codeJavaScript

const person = {
    name: 'Developer',
    // 错误示范:this 指向 window,而非 person 对象
    sayHi: () => {
        console.log(this.name);
    },
    // 正确示范:this 动态绑定到调用者 person
    sayHello: function() {
        console.log(this.name);
    }
};

person.sayHi();    // undefined
person.sayHello(); // Developer

第四部分:面试场景复盘(实战)

面试官提问:“请你谈谈箭头函数和普通函数的区别。”

高分回答范本

(建议采用“总-分-总”策略,逻辑清晰,覆盖全面)

1. 核心总结
“箭头函数是 ES6 引入的特性,它不仅提供了更简洁的语法,更重要的是彻底改变了 this 的绑定机制。简单来说,普通函数是动态绑定,箭头函数是词法绑定。”

2. 核心差异展开

  • 关于 this 指向(最重要)
    普通函数的 this 取决于调用方式,谁调用指向谁,可以通过 call/apply/bind 改变。
    而箭头函数没有自己的 this,它会捕获定义时上下文的 this,且永久绑定,即使使用 call 或 apply 也无法改变指向。这很好地解决了回调函数中 this 丢失的问题。
  • 关于构造能力
    箭头函数不能作为构造函数使用,不能使用 new 关键字,因为它没有 [[Construct]] 内部方法,也没有 prototype 原型对象。
  • 关于参数处理
    箭头函数内部没有 arguments 对象,如果需要获取不定参数,必须使用 ES6 的剩余参数 ...args。

3. 补充亮点与使用建议
“在实际开发中,箭头函数非常适合用在回调函数、数组方法(如 map、reduce)或者需要锁定 this 的场景(如 React 组件方法)。但在定义对象方法、原型方法或动态上下文场景中,为了保证 this 指向调用者,依然应该使用普通函数。”