箭头函数this绑定规则|与普通函数区别解析|实战应用避坑指南

箭头函数this绑定规则|与普通函数区别解析|实战应用避坑指南 一

文章目录CloseOpen

箭头函数this绑定的核心规则:为什么它和普通函数“不一样”?

要说箭头函数的this,得先从一个很多人都会踩的坑说起。去年我带的实习生小王,写了段这样的代码:他在一个对象里用箭头函数定义了个方法,想通过this访问对象的其他属性,结果打印出来this却是window。他当时特委屈:“我看箭头函数简洁才用的,怎么反而出错了?”其实这就是没搞懂箭头函数this绑定的核心规则——箭头函数根本没有自己的this,它的this是“抄”来的,从外层词法作用域“继承”过来的,而且在定义的时候就定死了,不管你后面怎么调用,它都不改

咱们先看个简单例子,你就能明白什么叫“定义时确定”。比如这样一段代码:

const obj = {

name: '测试对象',

normalFunc: function() {

console.log('普通函数this:', this.name); // 这里this指向obj

const arrowFunc = () => {

console.log('箭头函数this:', this.name); // 这里this继承自normalFunc的this,也就是obj

};

arrowFunc();

}

};

obj.normalFunc();

// 输出:普通函数this: 测试对象 箭头函数this: 测试对象

你看,箭头函数arrowFunc是在normalFunc里面定义的,所以它的this就“抄”了normalFunc运行时的this(也就是obj),不管后面怎么调用arrowFunc,这个this都不会变。哪怕你用call、apply去强行改,它也不为所动——这就是箭头函数和普通函数最大的区别之一,普通函数的this可是个“墙头草”,调用方式变了,它就跟着变。

那为什么箭头函数会这样?这就得说到“词法作用域”这个概念了。你可以把词法作用域理解成“代码写在哪里”决定的作用范围,就像你在小区里住哪个楼,地址是固定的,不会因为你去了趟超市就变了。箭头函数的this就遵循这个规则,它在哪个作用域里定义,就“认”那个作用域的this当自己的this,而且是“一认到底”。MDN文档里就明确说过:“箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this”(引用自MDN Web Docs关于箭头函数的说明{:nofollow})。

反观普通函数,它的this是“动态绑定”的,取决于“怎么调用”。比如普通函数直接在全局调用,this指向window(严格模式下是undefined);作为对象方法调用,this指向那个对象;用new调用,this指向新创建的实例;用call/apply/bind,this就指向传入的第一个参数。就像同一个人,在公司是员工,在家是父母,在商店是顾客,身份跟着场景变。我之前帮朋友改代码时,他写了个普通函数,一会儿当全局函数调,一会儿当对象方法调,结果this忽左忽右,数据怎么都不对,最后就是靠理清楚调用场景才解决的。

这里有个关键区别得拎清楚:箭头函数的this是“定义时绑定”,普通函数的this是“调用时绑定”。你可以把箭头函数的this想象成“出生时就跟着爸妈”,一辈子不变;普通函数的this则是“成年后自己选老板”,换个工作就换个“归属”。理解了这一点,很多this绑定的问题就迎刃而解了。

可能有人会问:“那箭头函数的this能不能改?”答案是不能。因为箭头函数没有自己的this,自然也就不能通过call、apply、bind这些方法改变this指向——这些方法对箭头函数来说是无效的。之前我同事不信邪,非要用bind给箭头函数绑this,结果控制台打印出来的还是原来的this,白折腾半天。这也是箭头函数和普通函数的一大区别:普通函数可以通过这些方法“换老板”,箭头函数不行。

箭头函数vs普通函数:实战场景下的选择与避坑指南

知道了原理,咱们再说说实际写代码时怎么选——什么时候用箭头函数,什么时候必须用普通函数?这可不是“哪个简洁用哪个”这么简单,用错了场景,bug能让你怀疑人生。我见过最夸张的,有个团队因为在构造函数里用了箭头函数,导致整个项目的实例化逻辑全错了,排查了三天才找到问题根源。

这些场景千万别用箭头函数,一用就踩坑!

先说说绝对不能用箭头函数的几个“雷区”。第一个就是构造函数——如果你想通过new关键字创建对象,那构造函数里绝对不能用箭头函数。为什么?因为箭头函数没有[[Construct]]内部方法,也没有prototype属性,用new调用会直接报错。我之前帮一个创业公司看代码,他们的开发者图省事,把类的构造函数写成了箭头函数,结果new的时候直接报“TypeError: XXX is not a constructor”,整个注册功能都崩了。普通函数作为构造函数时,this会指向新创建的实例,而箭头函数连自己的this都没有,自然没法当“构造者”。

第二个雷区是对象的方法。就像开头小王遇到的问题,如果你在对象里用箭头函数定义方法,那这个方法里的this就不是对象本身了,而是继承外层作用域的this。比如:

const user = {

name: '张三',

sayHi: () => {

console.log(你好,我是${this.name}); // this指向外层作用域(这里是window)

}

};

user.sayHi(); // 输出:你好,我是undefined(因为window.name通常是空的)

这里的sayHi是箭头函数,它的this继承自定义时的外层作用域(全局作用域的this,也就是window),所以this.name拿不到user对象的name属性。正确的做法是用普通函数定义对象方法,这时this才会指向调用方法的对象。后来我让小王把箭头函数改成普通函数,sayHi: function() { ... },问题立马解决了。

第三个要注意的是事件监听回调。比如给DOM元素绑定点击事件,如果你用箭头函数写回调,那this就不是当前元素了。举个例子:



const btn = document.getElementById('btn');

btn.addEventListener('click', () => {

console.log(this); // this指向外层作用域(这里是window)

this.style.color = 'red'; // 报错:Cannot set property 'color' of undefined

});

正常情况下,事件回调里的this应该是触发事件的元素(这里的btn),但箭头函数的this继承自外层的全局作用域(window),所以想通过this操作元素就会失败。这种场景必须用普通函数,或者在箭头函数里通过event.target获取元素。我自己早期写Vue项目时,就犯过在methods里用箭头函数定义方法的错——Vue组件里的methods默认this指向组件实例,但用了箭头函数后,this就指向了外层的window,导致拿不到data里的数据,排查了半天才发现是this绑定的问题。

这些场景用箭头函数,爽到飞起!

箭头函数也不是“洪水猛兽”,在很多场景下它简直是“救星”。最典型的就是异步回调,比如setTimeout、Promise的then/catch回调。以前写普通函数时,为了在回调里拿到正确的this,我们经常用const self = this或者.bind(this),麻烦得很。箭头函数一出来,这些“骚操作”都省了。

比如在Vue组件里发起请求:

// 普通函数写法,需要保存this

methods: {

fetchData() {

const self = this; // 保存this指向组件实例

axios.get('/api/data').then(function(response) {

self.data = response.data; // 这里的this是回调函数自己的,所以用self

});

}

}

// 箭头函数写法,直接用this

methods: {

fetchData() {

axios.get('/api/data').then(response => {

this.data = response.data; // this继承自fetchData的this,也就是组件实例

});

}

}

我现在写React或Vue组件,只要是异步回调,必用箭头函数,省得写self = this,代码也简洁多了。这就是箭头函数“词法作用域继承this”的好处——在定义回调时,外层的this就是组件实例,箭头函数直接继承过来,回调里就能直接用this访问实例属性。

另一个适合用箭头函数的场景是数组方法的回调,比如map、filter、reduce这些。这些回调函数通常不需要动态this,用箭头函数能让代码更短,可读性更高。比如处理数据时:

const numbers = [1, 2, 3, 4];

const doubled = numbers.map(num => num 2); // 简洁明了,不需要关心this

普通函数写的话就是numbers.map(function(num) { return num 2; }),多了function和return,箭头函数一行搞定。不过要注意,如果回调逻辑复杂,还是用普通函数加{}和return,别为了简洁牺牲可读性——我见过有人把十几行逻辑塞到一个箭头函数里,后面维护的人直接看懵了。

一张表帮你快速判断:该用箭头函数还是普通函数?

为了让你更直观地区分,我整理了一张对比表,把箭头函数和普通函数的核心区别列了出来,以后写代码前对照着看,就能避免90%的this绑定问题:

对比项 箭头函数 普通函数
this绑定 继承外层词法作用域的this,定义时确定,永不改变 动态绑定,取决于调用方式(全局、对象方法、new、call/apply/bind等)
构造函数 不能用new调用,会报错 可以用new调用,this指向新实例
prototype属性 没有prototype属性 有prototype属性,用于原型链继承
arguments对象 没有arguments对象,需要用rest参数(…args)代替 有arguments对象,包含所有实参
适用场景 异步回调、数组方法回调、不需要动态this的场景 构造函数、对象方法、需要动态this的场景

3步快速判断:这个函数该用箭头还是普通?

最后教你一个“三步判断法”,以后写函数前走一遍,就能立刻确定用哪种函数:

第一步,看这个函数需不需要动态this——如果函数里的this需要根据调用场景变化(比如对象方法要访问对象属性,构造函数要指向实例),那就用普通函数;如果this就是要固定不变(比如异步回调要访问外层作用域的变量),就用箭头函数。

第二步,看这个函数会不会被new调用——如果会,那必须用普通函数;箭头函数不能当构造函数。

第三步,看函数体简不简洁——如果是简单的单行返回(比如数组map回调),箭头函数的() => ...比普通函数的function() { return ... }简洁得多;如果函数体复杂,有多行逻辑和return,普通函数的可读性可能更好(当然箭头函数也能用{}和return,看个人习惯)。

前阵子我帮一个电商网站优化代码,他们之前所有函数都用普通函数,结果异步回调里全是const self = this,代码又长又乱。我按照这三步,把异步回调和数组方法的回调改成箭头函数,其他地方保留普通函数,代码量减少了20%,可读性还提高了,团队维护起来也方便多了。

其实箭头函数和普通函数没有“谁好谁坏”,关键是用对场景。就像螺丝刀和扳手,各有各的用处,用对了事半功倍,用错了拧坏螺丝还伤手。下次写代码遇到this绑定的问题,不妨回头看看这篇文章,对照着原理和场景分析,相信你一定能游刃有余地用好这两种函数。如果你之前也踩过箭头函数的坑,或者有其他this绑定的小技巧,欢迎在评论区分享,咱们一起把JavaScript的this玩明白!


你知道吗,在React类组件里写事件处理函数,好多新手一开始都爱用普通函数,结果一运行就傻眼——想在函数里用this.setState更新状态,控制台直接报错说“Cannot read property ‘setState’ of undefined”。我前两年带的实习生小李就踩过这坑,他写了个按钮点击事件,用普通函数定义handleClick,结果点按钮没反应,查了半天才发现this变成undefined了。其实这不能怪React“坑”,主要是类组件的普通方法默认就没绑定this,你把它当事件回调传给onClick的时候,它就跟组件实例“分家”了,跟咱们前面说的普通函数this动态绑定一个道理——调用方式变了,this自然就不指向组件实例了。尤其现在React默认开严格模式,普通函数的this连window都不是,直接成undefined,想访问state或props根本没戏。

那箭头函数为啥就能解决这问题呢?你想啊,箭头函数的this是继承外层词法作用域的,咱们在类组件里用箭头函数定义事件处理函数时,这函数是在类实例的作用域里定义的,所以它的this就“抄”了外层的this——也就是组件实例本身。这么一来,不管你怎么把这个箭头函数传来传去,this都死死指着组件实例,不用再费劲搞什么绑定。你可能见过有人在constructor里写this.handleClick = this.handleClick.bind(this),或者在render里用onClick={this.handleClick.bind(this)},前者写起来麻烦,后者每次render都会创建新函数,多了还影响性能。但用箭头函数就省事多了,直接handleClick = () => { … }这么定义,this稳稳的,代码还干净,这不就是咱们前面说的“箭头函数适合事件回调”的典型例子嘛。


箭头函数的this指向真的完全无法改变吗?

是的,箭头函数的this指向在定义时就已确定,继承自外层词法作用域,且永远无法通过调用方式改变。即使使用call、apply、bind等方法强行修改,this也不会变化。这是因为箭头函数本身没有自己的this绑定,只能继承外层作用域的this,一旦定义完成,this指向就固定不变。

如何快速判断一个函数中的this指向?

可通过“三步判断法”:第一步,看函数是否为箭头函数——若是,this继承外层词法作用域;若不是,进入第二步。第二步,看函数是否通过new调用(构造函数)——若是,this指向新创建的实例;若不是,进入第三步。第三步,看函数调用方式:全局调用指向window(严格模式下undefined),对象方法调用指向该对象,事件回调指向触发事件的元素,apply/call/bind调用指向传入的第一个参数。

在React类组件中,为什么推荐用箭头函数定义事件处理函数?

因为React类组件的普通方法默认不会绑定this,直接作为事件回调时,this可能指向undefined(严格模式下)。而箭头函数的this继承自组件实例的作用域(定义时外层作用域为类实例), 能确保this始终指向组件实例,无需手动绑定(如this.handleClick = this.handleClick.bind(this))。这也是文章提到的“箭头函数适合异步/事件回调”的典型场景。

箭头函数能否使用arguments对象?

不能。箭头函数没有自己的arguments对象,若需要获取函数参数,需使用rest参数(…args) 替代。例如普通函数中用arguments[0]获取第一个参数,箭头函数中可写成(…args) => args[0]。这是因为箭头函数设计初衷是简化短函数,省略了arguments、prototype等普通函数的特性。

箭头函数和普通函数在性能上有差异吗?

在大多数场景下,两者性能差异可忽略不计。但箭头函数因没有this绑定、arguments对象等特性,理论上创建时的内存占用略低于普通函数(省去了部分内部属性)。不过实际开发中,性能并非选择函数类型的主要因素,场景适配性(是否需要动态this、是否作为构造函数等) 才是更重要的判断标准。

0
显示验证码
没有账号?注册  忘记密码?