前言
箭头函数(Arrow Function)是 ES6 最受欢迎的新特性之一。它不仅让代码更加简洁,更重要的是它解决了 JS 中长期存在的 this 指向混乱问题。但你知道箭头函数真的“没有” this 吗?为什么它不能作为构造函数?本文带你一探究竟。
一、 背景:为什么要发明箭头函数?
在箭头函数出现之前,普通函数的 this 指向是动态的,取决于谁调用了它。这在回调函数中经常导致令人困惑的 Bug。
1. 对象方法中的 this 指向
在对象方法中调用this时,this指向该对象的方法,但是将对象方法作为独立函数调用时,this就会变成全局对象
const obj = {
a: 1,
func: function() {
console.log(this);
}
};
const test = obj.func;
obj.func(); // obj (正常,通过对象调用)
test(); // window (严格模式下为 undefined,this 丢失)
2. 构造函数的this指向
当使用 new 操作符调用构造函数时,this 指向新创建的实例对象。然而,如果不使用 new 而直接调用构造函数,this 就会指向全局对象
function MyObject() {
this.value = 5;
}
const obj1 = new MyObject();
console.log(obj1.value); // 5 (正常)
// ❌ 错误演示
const obj2 = MyObject(); // 没有 new,函数返回 undefined,且 window.value 被置为 5
console.log(obj2); // undefined
console.log(obj2.value); // 报错!Cannot read properties of undefined
二、 箭头函数的核心特性
1. 静态的 this(Lexical Scoping)
箭头函数没有自己的 this。它的 this 是它的this是继承上层函数作用域的,也就是说,它在定义时就捕获了其所在上下文的 this 值,而不是在执行时。
- 固定性:一旦绑定,永远不会改变。
- 不可修改:
call、apply、bind可以调用箭头函数。
const obj1 = {
name: 'obj1',
printName: function() {
console.log(this);
}
};
const obj2 = {
name: 'obj2',
printName: () => {
// 这里的 this 继承自 obj2 定义时的外层作用域(通常是 window)
console.log(this);
}
};
obj1.printName(); // {name: 'obj1', printName: ƒ}
obj2.printName(); // window
// 即使赋值给其他变量
const func1 = obj1.printName;
func1(); // window
const func2 = obj2.printName;
func2(); // window
2. 没有 arguments 对象
箭头函数中访问 arguments 会报错(或者引用外层函数的 arguments)。
解决方案:使用 ES6 的 剩余参数(Rest Parameters) ...args。
function func1() {
// 普通函数可以使用 arguments
console.log('func1:', arguments);
}
const func2 = (...args) => {
// 箭头函数使用剩余参数
console.log('func2 args:', args);
console.log(arguments); // ❌ 报错:arguments is not defined
};
func1(1, 2, 3); // Arguments(3) [1, 2, 3, ...]
func2(4, 5, 6); // [4, 5, 6] (真数组)
三、 为什么箭头函数不能作为构造函数?
这是面试中最底层的考题之一。
原因解析:
- 没有
this:构造函数需要将this绑定到新创建的实例上。箭头函数没有自己的this,它只能捕获外层的,无法被new内部的逻辑重新绑定。 - 没有
prototype:箭头函数对象上没有prototype属性,而new操作符的第二步是将实例的__proto__指向构造函数的prototype。
const ArrowPerson = (name) => {
this.name = name;
};
console.log(ArrowPerson.prototype); // undefined
const p = new ArrowPerson('Jack'); // TypeError: ArrowPerson is not a constructor
四、 避坑指南:哪些场景不适合用箭头函数?
- 定义对象的方法(如果方法内部需要操作对象属性)。
- DOM 事件回调(如果需要操作
this指向当前元素)。 - 原型链上的方法。
const button = document.getElementById('btn');
// ❌ 错误:this 指向 window,而不是 button 元素
button.addEventListener('click', () => {
this.classList.toggle('on');
});
// ✅ 正确:普通函数
button.addEventListener('click', function() {
this.classList.toggle('on');
});
五、 面试模拟题(挑战一下)
Q1:箭头函数的 this 指向哪里?
参考回答: 箭头函数没有自己的 this。它的 this 是继承上层函数作用域的,指向是固定的,指向定义该函数时所在的作用域中, 无法通过bind、call、apply来改变。
Q2:普通函数和箭头函数的主要区别有哪些?
参考回答:
- this 指向:普通函数看调用方式;箭头函数看定义位置(词法作用域)。
- 构造函数:箭头函数不能
new,因为没有prototype且无法绑定this。 - arguments:箭头函数没有
arguments,需用 rest 参数...args。
Q3:如下代码输出什么?
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a();
obj.b();
参考回答:
obj.a()输出'OBJ'(普通函数,obj调用)。obj.b()输出'GLOBAL'(箭头函数,继承自全局作用域的this)。