JavaScript 箭头函数究竟是什么?
JavaScript箭头函数大致相当于Python中的lambda表达式以及Ruby中使用的块状语法元素或可扩展对象(prot REC)。
这些是lambda表达式,在编程语言中它们具有独特的语法规则。lambda表达式接收特定数量的输入参数,并在被定义时所使用的上下文中执行操作。这些上下文通常是被定义函数或代码块所限定的区域。
让我们依次分解这些部分。
箭头函数语法
该类函数通常呈现出一种统一的架构设计,在特定场景下,则可通过多种途径进行优化与简化。 核心结构如下所示:
JavaScript 代码:
(argument1, argument2, ... argumentN) => {
// function body
}
括号内的是参数列表,后跟“胖箭头”(=>),最后是函数体。
这与其说传统函数也不为过,并非我们只是跳过了 function 关键字并添加了Right Arrow(=>)。
然而,有许多方法可以简化箭头函数。
首先,在某些情况下(如当函数体内为单一表达式时),可以直接将整个表达式放置于同一行而不必添加额外的大括号。
该函数会返回该表达式的计算结果。
如以下两种情况所示:
JavaScript 代码:
const add = (a, b) => a + b;
其次,如果只有一个参数,你甚至可以省略参数的括号。例如:
JavaScript 代码:
const getFirst = array => array[0];
正如您所看到的,这是一些非常简洁的语法,我们将重点介绍后面的好处。
高级语法
有一些高级语法可以了解一下。
请记住,在尝试使用内联单行表达式语法时,请注意如果您的代码返回的是一个对象字面量,则可能出现以下情形:那么这种情形下,预期的结果应当类似于以下所示:
JavaScript 代码:
(name, description) => {name: name, description: description};
问题是这种语法比较含糊不清,容易引起歧义 : 看起来好像你正试图创建一个传统的函数体。 如果你碰巧想要一个对象的单个表达式,请用括号包裹该对象:
JavaScript 代码:
(name, description) => ({name: name, description: description});
被严格限制的封闭性作用域 在与其他形式函数相比 箭头函数不具备独立且私有的执行期
实际上,这意味着 this 和 arguments 都是从它们的父函数继承而来的。
例如,使用和不使用箭头函数比较以下代码:
JavaScript 代码:
const test = {
name: 'test object',
createAnonFunction: function() {
return function() {
console.log(this.name);
console.log(arguments);
};
},
createArrowFunction: function() {
return () => {
console.log(this.name);
console.log(arguments);
};
}
};
我们有一个简单的 test 对象,有两个方法 – 每个方法都返回一个匿名函数。
不同之处在于第一个方法使用传统函数表达式,而后者中使用箭头函数。
如果我们在相同参数设置并进行控制台运行时的结果会呈现显著差异
JavaScript 代码:
> const anon = test.createAnonFunction('hello', 'world');
> const arrow = test.createArrowFunction('hello', 'world');
> anon();
undefined
{}
> arrow();
test object
{ '0': 'hello', '1': 'world' }
第一个匿名函数拥有自己的函数上下文。因此,在调用该函数时, test对象中的this.name字段无法获得相应的引用, 并且未创建该函数所需的参数。
另一种情况下,在创建箭头函数时所使用的上下文与原函数相同,并且该上下文能够获取或访问参数以及测试对象。
采用箭头函数来优化您的代码;传统的 lambda 函数的一个典型应用就是用于遍历列表中的各项;现在已经借助 JavaScript 的箭头函数来实现这一功能
举个例子来说,在这种情况下你可以使用箭头函数来进行映射操作来逐项处理每一个元素这会使得你的代码更加简洁高效
JavaScript 代码:
const words = ['hello', 'WORLD', 'Whatever'];
const downcasedWords = words.map(word => word.toLowerCase());
一个非常常见的例子是提取对象中的某个特定值:
JavaScript 代码:
const names = objects.map(object => object.name);
类似地,在采用现代迭代风格替代传统的 for 循环时, 通常我们会采用 forEach 循环, 箭头函数使得 this 来自于父级更加直观
类似地,在使用 forEach 替代传统的 for 循环时,箭头函数可以直接反映出 this来自父级结构。
JavaScript 代码:
this.examples.forEach(example => {
this.runExample(example);
});
promises 和 promise 链
箭头函数的另一个重要的是让代码更加简洁明了地处理异步操作。
通过Promises技术的应用,在实际操作中实现异步代码的高效管理变得极为简便(即便你以愉悦的心情频繁使用async/await关键字,在深入理解其背后原理之前仍需意识到它始终建立在 promises 的基础之上。)
然而,在使用 promises 时仍然必须明确地指定其执行位置,在异步代码中或者是在调用完成后自动执行的函数中。
这是箭头函数的最佳位置。特别地,在您生成具有状态的函数时,并且希望引用对象中的某些内容时,请将其放置在这里。例如以下所示:
JavaScript 代码:
this.doSomethingAsync().then((result) => {
this.storeResult(result);
});
对象转换
箭头函数的另一个常见且极其强大的用途是封装对象转换。
在Vue框架中提供了一种常用方法:一种常用方法是通过调用mapState方法将Vuum存储的所有组件状态直接注入到对应的Vue组件中
这涉及构建一组"mappers" ,这些"mappers" 负责从原始完整的state对象实例中提取与所述组件相关的数据内容。
这些简单的转换使用箭头函数再合适不过了。比如:
JavaScript 代码:
export default {
computed: {
...mapState({
results: state => state.results,
users: state => state.users,
});
}
}
你不应在任何情况下使用箭头函数。
使用箭头函数并非所有场合都合适。
这不仅会降低你的效率和代码可读性,
还会让你的代码难以理解。
该方法主要针对的对象是通过实例化的对象来进行操作。 从这个具体的函数执行环境来看,在实际应用中我们可以将其视为一个典型的场景来深入理解其运行机制。
在一段时间内,我们采用Class(类)属性语法与箭头函数的结合方式来实现自动绑定功能。这种技术手段不仅适用于构建事件处理程序等场景的应用实例,在这些应用中仍需确保方法被正确地关联到相应的类对象上。
这看起来像是这样的:
JavaScript 代码:
class Counter {
counter = 0;
handleClick = () => {
this.counter++;
}
}
Even if handleClick is triggered by event handlers rather than within the Counter instance, it can still retain access to the instance data.
这种方法的缺点很多,在本文中很好地记录。
虽然采用这种方法确实为您提供了包含绑定函数的快捷方式。然而此功能以非直观的方式执行多个操作。如果将此对象用作基类进行继承,则会带来诸多问题。这会导致一系列问题。
相反,使用常规函数,如果需要,将其绑定到构造函数中的实例:
JavaScript 代码:
class Counter {
counter = 0;
handleClick() {
this.counter++;
}
constructor() {
this.handleClick = this.handleClick.bind(this);
}
}
作为可能让你遇到麻烦的一个常见问题,在复杂的函数深层调用链中使用箭头函数可能会导致各种潜在的问题
核心原因与匿名函数相同 – 它们给出了非常糟糕的堆栈跟踪。
如果一个函数仅负责向下级操作,在迭代器内部进行操作也不会造成太大问题。然而,在这种情况下将所有函数都定义为箭头式,并要求它们之间进行交互调用将会导致严重的困难。当遇到一个问题时(例如:程序运行出现异常),通常只会收到简短的提示信息(如:堆栈溢出或类型转换失败)。这些情况通常只会返回简短的提示信息(如:堆栈溢出或类型转换失败)。例如:当尝试对不可逆操作进行反向操作时会触发此类错误。
JavaScript 代码:
{anonymous}()
{anonymous}()
{anonymous}()
{anonymous}()
{anonymous}()
这种类型的函数涉及带有动态上下文的操作。
箭头函数可能导致您遇到麻烦的最后一类情况是什么时候?这通常是由于动态绑定导致的。
当在这些地方调用箭头函数时,则无法实现动态绑定功能,并且当你(或稍后的人)在编写代码时遇到问题时,原因可能包括逻辑错误或其他不可预见的问题。
一些典型的例子:
该程序通过指定this为事件.currentTarget属性来执行调用。
如果继续使用jQuery,则大部分jQuery方法会把this设置为已被选中的dom元素。
如果采用Vue.js,则其方法和计算函数通常会把this设置为此前创建的Vue组件。
当然,在jQuery或Vue的情况下你可能试图故意使用箭头函数覆盖这种行为。然而这样做通常会导致正常运行被干扰让人困惑:为何看似与周围代码相似的行为无法生效?
总结如下:箭头函数是 JavaScript 语言的一个不可或缺的补充,并且使得代码更加符合人们的阅读习惯。
然而,在如同其他各项特性一样的情况下(如同其他各项特性一样的情况下),它们也都具备优势与不足(并不仅仅)。我们应当将其视为我们工具箱中增添的一个额外工具(作为一个额外的工具),而非将其视为取代现有功能的一份通用方案(一种通用方案)。
英文原文:https://zendev.com/2018/10/01/javascript-arrow-functions-how-why-when.html
