098 Regular Functions vs. Arrow Functions(常规函数与箭头函数)笔记

一、核心主题

本章节聚焦 JavaScript 中常规函数与箭头函数的核心差异,重点围绕this关键字指向规则、arguments关键字可用性展开,结合实际代码案例解析常见陷阱,并给出不同场景下的函数选择建议,帮助开发者规避错误、正确运用两种函数。

二、箭头函数作为对象方法的陷阱

2.1 问题表现

当用箭头函数定义对象的方法时,无法正确访问对象自身的属性,会出现undefined或意外获取全局变量的情况。

2.2 代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义包含箭头函数方法的对象
const Jonas = {
name: "Jonas", // 对象自身属性
// 用箭头函数定义greet方法
greet: () => {
console.log(`嘿,${this.name}`); // 试图访问对象的name属性
console.log("this指向:", this); // 查看this指向
},
};

// 调用方法,结果不符合预期
Jonas.greet();
// 输出1:嘿,undefined(未获取到Jonas对象的name)
// 输出2:this指向:Window(全局对象)

// 若存在全局变量(用var声明),会意外访问全局变量
var name = "Matilda"; // var声明的全局变量会挂载到Window
Jonas.greet();
// 输出1:嘿,Matilda(错误获取全局name)
// 输出2:this指向:Window

2.3 原理分析

  1. 箭头函数无自身this:箭头函数不绑定自己的this,会继承父作用域的this
  2. 对象字面量不创建作用域Jonas是对象字面量({}),并非代码块,不会生成新作用域,其内部的箭头函数greet的父作用域是全局作用域
  3. 全局作用域的this:浏览器环境中,全局作用域的this指向Window对象,因此箭头函数中的this最终指向Window
  4. var 声明的副作用:用var声明的全局变量会自动挂载到Window对象上,导致箭头函数误访问全局变量而非对象自身属性。

2.4 解决方案

永远不要用箭头函数作为对象的方法,应使用常规函数(函数表达式)定义对象方法,确保this指向调用方法的对象:

1
2
3
4
5
6
7
8
9
10
11
12
const Jonas = {
name: "Jonas",
// 用常规函数表达式定义方法
greet: function () {
console.log(`嘿,${this.name}`);
console.log("this指向:", this);
},
};

Jonas.greet();
// 输出1:嘿,Jonas(正确获取对象name)
// 输出2:this指向:{name: "Jonas", greet: ƒ}(指向Jonas对象)

三、方法内部常规函数的this问题及解决办法

3.1 问题表现

在对象的常规方法内部定义另一个常规函数时,内部常规函数的this会指向undefined(严格模式下),导致无法访问外部方法的this(即对象自身)。

3.2 代码案例(问题场景)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Jonas = {
name: "Jonas",
year: 1990, // 出生年份
// 常规方法:计算年龄并判断是否为千禧一代
calcAge: function () {
// 方法内部定义常规函数isMillennial
function isMillennial() {
// 试图访问Jonas对象的year属性
console.log(this.year >= 1981 && this.year < 1996);
console.log("内部函数this指向:", this);
}
isMillennial(); // 调用内部常规函数
},
};

// 调用方法,报错
Jonas.calcAge();
// 输出1:Uncaught TypeError: Cannot read properties of undefined (reading 'year')
// 输出2:内部函数this指向:undefined(严格模式下)

3.3 原理分析

  1. 常规函数的this绑定规则:常规函数的this指向调用者,若直接调用(无明确调用者,如isMillennial()),在严格模式下thisundefined,非严格模式下指向Window
  2. 作用域隔离:方法内部的常规函数isMillennial有自己的作用域,其this不继承外部方法calcAgethis(即 Jonas 对象)。

3.4 解决方案

方案 1:ES6 之前 —— 用变量保存外部this(传统方案)

通过在外部方法中定义变量(如selfthat),保存外部的this(指向对象),内部函数通过作用域链访问该变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Jonas = {
name: "Jonas",
year: 1990,
calcAge: function () {
const self = this; // 保存外部this(指向Jonas对象)
function isMillennial() {
// 访问保存的self,而非自身this
console.log(self.year >= 1981 && self.year < 1996);
console.log("内部函数访问的self指向:", self);
}
isMillennial();
},
};

Jonas.calcAge();
// 输出1:true(1990年属于千禧一代)
// 输出2:内部函数访问的self指向:{name: "Jonas", year: 1990, calcAge: ƒ}

方案 2:ES6 之后 —— 用箭头函数(推荐方案)

利用箭头函数 “继承父作用域this” 的特性,将内部函数定义为箭头函数,使其this直接指向外部方法calcAgethis(即 Jonas 对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Jonas = {
name: "Jonas",
year: 1990,
calcAge: function () {
// 内部定义箭头函数,继承父作用域(calcAge)的this
const isMillennial = () => {
console.log(this.year >= 1981 && this.year < 1996);
console.log("箭头函数this指向:", this);
};
isMillennial();
},
};

Jonas.calcAge();
// 输出1:true
// 输出2:箭头函数this指向:{name: "Jonas", year: 1990, calcAge: ƒ}

四、arguments关键字的可用性差异

4.1 核心规则

  • 常规函数:可访问arguments关键字,它是一个包含所有传入参数的类数组对象(有索引和length属性,但无数组方法如forEach),支持接收多于定义参数数量的参数。
  • 箭头函数:不可访问arguments关键字,调用时会报错 “arguments is not defined”。

4.2 代码案例(常规函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 常规函数表达式:计算所有传入参数的和
const addNumbers = function (a, b) {
console.log("传入的所有参数:", arguments); // 查看arguments
let sum = 0;
// 遍历arguments(类数组),累加所有参数
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
};

// 调用函数:传入2个参数(符合定义)
console.log("和为:", addNumbers(2, 5));
// 输出1:传入的所有参数:Arguments(2) [2, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 输出2:和为:7

// 调用函数:传入3个参数(多于定义)
console.log("和为:", addNumbers(2, 5, 8));
// 输出1:传入的所有参数:Arguments(3) [2, 5, 8, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 输出2:和为:15

4.3 代码案例(箭头函数,报错场景)

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
// 箭头函数:试图访问arguments
const addNumbersArrow = (a, b) => {
console.log("传入的所有参数:", arguments); // 报错
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
};

// 调用箭头函数,报错
addNumbersArrow(2, 5);
// 输出:Uncaught ReferenceError: arguments is not defined

4.4 现代替代方案

arguments在现代 JavaScript 中重要性下降,推荐用剩余参数(...rest 替代,剩余参数是真正的数组,支持所有数组方法,且箭头函数和常规函数都可使用:

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 常规函数用剩余参数
const addNumbersRest1 = function (...numbers) {
console.log("剩余参数(数组):", numbers);
return numbers.reduce((sum, num) => sum + num, 0); // 用数组方法reduce求和
};

// 箭头函数用剩余参数
const addNumbersRest2 = (...numbers) => {
console.log("剩余参数(数组):", numbers);
return numbers.reduce((sum, num) => sum + num, 0);
};

console.log(addNumbersRest1(2, 5, 8));
// 输出1:剩余参数(数组):[2, 5, 8]
// 输出2:15

console.log(addNumbersRest2(3, 6));
// 输出1:剩余参数(数组):[3, 6]
// 输出2:9

五、常规函数与箭头函数使用总结

5.1 核心差异对比表

特性 常规函数(Regular Functions) 箭头函数(Arrow Functions)
this绑定 绑定调用者(谁调用指向谁) 无自身this,继承父作用域this
arguments关键字 可访问(类数组对象) 不可访问(报错)
构造函数用法 可作为构造函数(用new创建实例) 不可作为构造函数(用new报错)
prototype属性 prototype属性 prototype属性
简写形式 无(需写function关键字) 有(省略functionreturn和大括号,单参数可省括号)

5.2 场景化选择建议

  1. 对象方法:用常规函数(确保this指向对象自身),禁止用箭头函数。

    ✅ 正确:greet: function() {}greet() {}(ES6 对象方法简写)

    ❌ 错误:greet: () => {}

  2. 方法内部嵌套函数:用箭头函数(继承外部this,无需额外变量保存),或用常规函数 +self(传统方案)。

    ✅ 推荐:const innerFn = () => {}

    ✅ 兼容:const self = this; function innerFn() {}

  3. 回调函数(如setTimeout、数组方法):优先用箭头函数(避免this指向丢失)。

    案例:

    javascript

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const Jonas = {
    name: "Jonas",
    delayGreet: function () {
    // 箭头函数作为setTimeout回调,this指向Jonas
    setTimeout(() => {
    console.log(`嘿,${this.name}`);
    }, 1000);

    // 若用常规函数,this指向Window(错误)
    setTimeout(function () {
    console.log(`嘿,${this.name}`); // 输出“嘿,undefined”
    }, 2000);
    },
    };
    Jonas.delayGreet();
  4. 需要访问多参数:用常规函数(arguments剩余参数(...rest(推荐,箭头函数也可用)。

    ✅ 现代方案:function fn(...args) {}const fn = (...args) => {}

  5. 构造函数:用常规函数(箭头函数无prototype,无法创建实例)。

    ✅ 正确:function Person(name) { this.name = name; }

    ❌ 错误:const Person = (name) => { this.name = name; }(用new Person()报错)