少年,不要滥用箭头函数啊

在ES6大行其道的今天,不应用点ES6特性似乎有些政治不正确。最近刚好有个Node的项目,最低要支持到nodejs 4.0,在node.green看了下ES6的支持度,我想使用的特性基本都有支持,遂决定在新项目中采用ES6来写。

当然第一件事情就是毫不留情地消灭var,项目中能用const的地方不用let,能用let的地方不用var。

第二件事情就是使用劳动人民喜闻乐见的箭头函数替代function。当我心满意足地看到满屏的=>时,现实给了我一记响亮的耳光——改过之后的程序错误百出!

所以,当我们使用箭头函数时,一定要搞清楚箭头函数是什么回事,适用于什么场景。本文就针对以上问题来讨论下箭头函数。

箭头函数是什么?

箭头函数的语法我就不讲了,相信大家都见识过。跟我一样,大家喜欢箭头函数90%的原因是它好看。除了好看,它是不是与function等价呢?肯定不等价,因为TC39不可能仅因为好看而引入一个语法糖(class除外)。

箭头函数的渊源可以追溯到上古时期一个叫lambda演算的东西。lambda演算是数学家提出来的,有些数学家跟我们程序员一样也很懒,数学定理那么多,今天要证三角定律,明天要证勾股定律,累不累!那能不能将所有的证明问题用一个统一的体系进行形式化描述,然后由机器来完成自动推导呢?lambda演算就是干这个的,图灵也搞了一套体系叫图灵机,两者是等价的。

关于lambda演算说了这么多,好像跟今天要讲的箭头函数没什么关系?其实是有关系的,lambda演算深刻影响了箭头函数的设计。数学家们喜欢用纯函数式编程语言,纯函数的特点是没有副作用,给予特定的输入,总是产生确定的输出,甚至有些情况下通过输出能够反推输入。要实现纯函数,必须使函数的执行过程不依赖于任何外部状态,整个函数就像一个数学公式,给定一套输入参数,不管是在地球上还是火星上执行都是同一个结果。

箭头函数要实现类似纯函数的效果,必须剔除外部状态。所以当你定义一个箭头函数,在普通函数里常见的thisargumentscaller是统统没有的。

箭头函数没有this

箭头函数没有this,那下面的代码明显可以取到this啊:

function foo() {
  this.a = 1
  let b = () => console.log(this.a)

  b()
}

foo()  // 1

以上箭头函数中的this其实是父级作用域中的this,即函数foothis。箭头函数引用了父级的变量,构成了一个闭包。以上代码等价于:

function foo() {
  this.a = 1

  let self = this
  let b = () => console.log(self.a)

  b()
}

foo()  // 1

箭头函数不仅没有this,常用的arguments也没有。如果你能获取到arguments,那它一定是来自父作用域的。

function foo() {
  return () => console.log(arguments[0])
}

foo(1, 2)(3, 4)  // 1

上例中如果箭头函数有arguments,就应该输出的是3而不是1。

一个经常犯的错误是使用箭头函数定义对象的方法,如:

let a = {
  foo: 1,
  bar: () => console.log(this.foo)
}

a.bar()  //undefined

以上代码中,箭头函数中的this并不是指向a这个对象。对象a并不能构成一个作用域,所以再往上到达全局作用域,this就指向全局作用域。如果我们使用普通函数的定义方法,输出结果就符合预期,这是因为a.bar()函数执行时作用域绑定到了a对象。

let a = {
  foo: 1,
  bar: function() { console.log(this.foo) }
}

a.bar()  // 1

另一个错误是在原型上使用箭头函数,如:

function A() {
  this.foo = 1
}

A.prototype.bar = () => console.log(this.foo)

let a = new A()
a.bar()  //undefined

同样,箭头函数中的this不是指向A,而是根据变量查找规则回溯到了全局作用域。同样,使用普通函数就不存在问题。

通过以上说明,我们可以看出,箭头函数除了传入的参数之外,真的是什么都没有!如果你在箭头函数引用了thisarguments或者参数之外的变量,那它们一定不是箭头函数本身包含的,而是从父级作用域继承的。

什么情况下该使用箭头函数

到这里,我们可以发现箭头函数并不是万金油,稍不留神就会踩坑。

至于什么情况该使用箭头函数,《You Don’t Know JS》给出了一个决策图: arrow

以上决策图看起来有点复杂,我认为有三点比较重要:

  1. 箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如用在mapreducefilter的回调函数定义中;
  2. 不要在最外层定义箭头函数,因为在函数内部操作this会很容易污染全局作用域。最起码在箭头函数外部包一层普通函数,将this控制在可见的范围内;
  3. 如开头所述,箭头函数最吸引人的地方是简洁。在有多层函数嵌套的情况下,箭头函数的简洁性并没有很大的提升,反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数。
<think>好的,我现在需要解释箭头函数中的this问题。首先,用户已经了解过普通函数中的arguments,现在转向箭头函数的this,应该要对比两者的不同。 首先,我得回忆箭头函数和普通函数在this处理上的区别。普通函数的this是动态绑定的,取决于调用方式,而箭头函数没有自己的this,继承外层作用域的this。这点很关键,需要明确说明。 用户可能想知道为什么箭头函数要这样设计,可能涉及到词法作用域和避免常见错误,比如在回调中this丢失的问题。需要举例子说明,比如在setTimeout或者事件处理函数中,普通函数需要bind或者用that=this,而箭头函数直接继承。 另外,箭头函数的this在定义时确定,不会改变。这点和普通函数不同,普通函数的this可以随调用方式改变。需要举例子对比,比如对象方法中的普通函数和箭头函数的不同表现。 可能用户会遇到的问题包括在对象方法中使用箭头函数导致this指向错误,或者在原型方法中使用箭头函数的问题。需要指出这些使用场景的注意事项。 还要提到call、apply、bind对箭头函数无效,因为它们不改变箭头函数的this。这点需要强调,并提供例子说明。 最后,总结箭头函数适用场景,比如需要固定this的时候,或者需要简洁语法的地方,同时指出不适合的场景,比如需要动态this的方法或构造函数。 需要结构清晰,分点说明,可能用对比表格,代码示例来帮助理解。确保语言通俗易懂,避免术语混乱,同时覆盖常见误区和解决方案。</think>### 一、箭头函数 `this` 的核心特性 #### 1. **词法作用域绑定** - **无独立 `this`**:箭头函数 **不创建自己的 `this`**,而是直接继承外层(父级)普通函数或全局作用域的 `this`。 - **定义时确定**:箭头函数的 `this` 在函数 **定义时确定**(而非调用时),且 **永不改变**。 ```javascript const obj = { value: 10, normalFn: function() { console.log(this.value); // 输出10(this指向obj) }, arrowFn: () => { console.log(this.value); // 输出undefined(this继承外层全局作用域,浏览器中指向window) } }; obj.normalFn(); // ✅ 正常 obj.arrowFn(); // ❌ 不符合预期 ``` --- ### 二、对比普通函数与箭头函数的 `this` | 特性 | 普通函数(`function`) | 箭头函数 | |---------------------|------------------------------------|----------------------------------| | **`this`绑定方式** | 动态绑定(由调用方式决定) | 静态绑定(继承外层作用域的 `this`) | | **能否修改`this`** | ✅ 可通过 `call/apply/bind` 修改 | ❌ 固定不可变 | | **适用场景** | 需要动态 `this`(如对象方法) | 需要固定 `this`(如回调函数) | --- ### 三、典型场景分析 #### 1. **回调函数中的 `this` 丢失问题(经典案例)** - **普通函数的问题**: ```javascript const timer = { value: 100, start: function() { setTimeout(function() { console.log(this.value); // 输出undefined(this指向window) }, 1000); } }; timer.start(); ``` - **箭头函数的解决方案**: ```javascript const timer = { value: 100, start: function() { setTimeout(() => { console.log(this.value); // 输出100(继承外层start函数的this) }, 1000); } }; timer.start(); ``` #### 2. **对象方法中的陷阱** - **错误用法**(箭头函数直接作为对象方法): ```javascript const counter = { count: 0, increment: () => { this.count++; // this指向全局对象,无法操作count } }; counter.increment(); // ❌ 无效操作 ``` - **正确方案**(普通函数 + 闭包或方法定义): ```javascript const counter = { count: 0, increment: function() { this.count++; // ✅ 正确指向counter对象 } }; ``` #### 3. **DOM 事件处理函数** - **普通函数的动态 `this`**: ```javascript button.addEventListener("click", function() { console.log(this); // 输出按钮元素(this自动绑定触发事件的元素) }); ``` - **箭头函数的静态 `this`**: ```javascript button.addEventListener("click", () => { console.log(this); // 输出外层作用域的this(如全局window) }); ``` --- ### 四、强制修改箭头函数 `this` 的无效性 #### 1. **`call/apply/bind` 无法改变箭头函数的 `this`** ```javascript const outerThis = { value: 200 }; const arrowFn = () => console.log(this.value); // 尝试修改this(无效) arrowFn.call(outerThis); // 输出undefined(this仍指向外层全局作用域) ``` #### 2. **间接修改方案** 若需动态绑定 `this`,必须改用普通函数: ```javascript function dynamicThisFn() { console.log(this.value); } dynamicThisFn.call({ value: 999 }); // 输出999 ``` --- ### 五、何时使用箭头函数? #### 1. **推荐场景** - **回调函数**(如 `setTimeout`、`Promise`、数组方法): ```javascript [1, 2, 3].forEach(num => console.log(num * this.scale), context); ``` - **需要固定 `this` 的操作**(如 React 类组件中的事件处理): ```javascript class MyComponent extends React.Component { handleClick = () => { console.log(this.props); // 正确指向组件实例 }; } ``` #### 2. **避免场景** - **对象方法定义**(需访问对象自身属性时) - **构造函数**(箭头函数不能作为构造函数) - **需要动态 `this` 的方法**(如原型方法、事件处理函数需绑定元素) --- ### 六、常见误区与解决方案 #### 1. **误区:在Vue/React组件中滥用箭头函数** - **错误示例**(Vue methods): ```javascript methods: { fetchData: () => { this.loading = true; // this指向全局,无法操作组件数据 } } ``` - **正确方案**(普通函数或类属性语法): ```javascript // Vue methods: { fetchData() { this.loading = true; // ✅ 正确指向组件实例 } } // React类组件 class MyComponent extends React.Component { fetchData = () => { this.setState({ loading: true }); // ✅ 正确(类属性箭头函数绑定实例) }; } ``` #### 2. **误区:混淆箭头函数与普通函数的 `this`** - **代码对比**: ```javascript function Person(name) { this.name = name; this.sayName = function() { console.log(this.name); // ✅ 动态this(指向实例) }; this.sayNameArrow = () => { console.log(this.name); // ✅ 箭头函数继承Person构造函数的this(指向实例) }; } const person = new Person("John"); person.sayName(); // 输出"John" person.sayNameArrow(); // 输出"John" ``` --- ### 总结 - **箭头函数的 `this`**:静态继承外层作用域,无法修改,适合需要固定上下文的场景。 - **普通函数的 `this`**:动态绑定,灵活但易出错,适合需要动态 `this` 的场景。 - **关键原则**: - 对象方法、构造函数 → **用普通函数** - 回调函数、需要固定 `this` → **用箭头函数** - 避免在需要动态 `this` 的场景中使用箭头函数
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值