【Dmitri Pavlutin】什么时候不用箭头函数

译者:道奇

作者:Dmitri Pavlutin

原文: When 'Not' to Use Arrow Functions

很高兴看到自己所使用的编程语言一天天的发展,从错误中学习、寻找更好的实现方式,创建新的功能都使得它一个版本到另一个版本的进步。

这些年的 JavaScript 就是这样发展着,当 ECMAScript6 将这门语言的可用性带到了一个新的水平:箭头函数,类,还有 更多 ,这非常棒!

这其中最有用的一个功能是箭头函数,有很多优秀的文章描述了它的上下文的透明性和简短的语法,如果你还不熟悉 ES6 ,可以读一下 这篇 文章。

但但每枚奖牌都有两面。通常新的功能的会带来一些混乱,其中之一就是箭头函数会被乱用。

本文将介绍应该绕过箭头函数的一些场景,而是使用旧的 函数表达式 或更新的 简写方法语法 ,并注意缩短,以免影响代码的可读性。

1. 在对象上定义方法

JavaScript 中,方法是存储在对象属性上的函数,当调用方法时, this 就是方法所属的对象。

1a. 对象字面量

因为箭头函数的语法简短,在方法定义中去使用它是非常诱人的。下面就试一下:

const calculate = {
  array: [1, 2, 3],
  sum: () => {
    console.log(this === window); // => true
    return this.array.reduce((result, item) => result + item);
  }
};
console.log(this === window); // => true
// Throws "类型异常:无法读取undefined的属性'reduce'"
calculate.sum();
复制代码

通过箭头函数定义 calculate.sum 方法,但调用 calculate.sum() 抛出了 类型异常 的错误,因为 this.array 等于 undefined 。 当调用 calculate 对象上的 sum 方法时,上下文依然是 window ,这是因为在词法上将上下文绑定到了 window 对象上。 执行 this.array 就相当于执行值 window.array ,而它的值是 undefined

解决方法就是使用函数表达式或简短的语法进行函数定义(用于 ECMAScript6 上),在这种情况下, this 取决于调用者而不是封闭的上下文,看一下修改后的代码:

const calculate = {  
  array: [1, 2, 3],
  sum() {
    console.log(this === calculate); // => true
    return this.array.reduce((result, item) => result + item);
  }
};
calculate.sum(); // => 6
复制代码

因为 sum 是个常规函数, calculate.sum 调用中的 thiscalculate 对象, this.array 是数组引用,因此元素的求和正确的计算结果是: 6

1b. 对象原型

同样的规则会应用到在原型对象上,如果用箭头函数定义 sayCatName 方法,会得到不正确的上下文 window

function MyCat(name) {
  this.catName = name;
}
MyCat.prototype.sayCatName = () => {
  console.log(this === window); // => true
  return this.catName;
};
const cat = new MyCat('Mew');
cat.sayCatName(); // => undefined
复制代码

使用老式函数表达式:

function MyCat(name) {
  this.catName = name;
}
MyCat.prototype.sayCatName = function() {
  console.log(this === cat); // => true
  return this.catName;
};
const cat = new MyCat('Mew');
cat.sayCatName(); // => 'Mew'
复制代码

sayCatName 作为方法进行调用: cat.sayCatName() 时,它将上下文改成了 cat 对象。

2. 通过动态上下文回调函数

thisJavaScript 中很棒的一个特性,它允许根据调用函数的方式更改上下文。通常,上下文是调用发生的目标对象,这使代码更加 自然 ,就像“这个对象正在发生着一些事”。

然而,箭头函数在声明的时候就静态的绑定了上下文,这使得它不可能再变成动态了。这种情况下,这就是奖牌的另一面,词法 this 已经不是必要的了。

将事件监听器绑定到 DOM 元素上是客户端编程的常见任务,事件会触发带 this 的处理函数作为目标元素,动态的上下文就会比较方便使用。

下面的例子是使用箭头函数作为处理器:

const button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});
复制代码

在全局上下文中定义的箭头函数中的 thiswindow ,当点击事件触发时,浏览器会试着去调用 button 上下文中的处理函数,但是箭头函数没改变它的预定义上下文, this.innerHTML 等价于 window.innerHTML ,这并没有意义。

你必须应用函数表达式,它允许根据目标元素来改变 this

const button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});
复制代码

当用户点击按钮时,处理函数中的 thisbutton ,因此 this.innerHTML = 'Clicked button' 正确的修改按钮的文本以反映对应的点击状态。

3. 调用构造函数

在构造函数调用中 this 是新创建的那个对象,当执行 new MyFunction() 时,构造函数 MyFunction 的上下文是新对象: this instanceof MyFunction === true

注意,箭头函数不能用作构造函数, JavaScript 通过抛出异常来明确阻止这样做。无论如何, this 是从封闭的上下文中设置的,而不是新建的对象。也就是说,箭头函数构造函数调用没有意义,而且会引起歧义。

看一下如果这样做会发生什么:

const Message = (text) => {
  this.text = text;
};
// 抛出“类型异常:Message不是构造函数
const helloMessage = new Message('Hello World!');
复制代码

执行 new Message('Hello World!')Message 是 箭头函数, JavaScript 抛出 类型异常Message 不能用于构造函数。

这种情况下, ECMAScript 6 会报出长长的错误信息,相比之前 JavaScript 版本出错没有任何提示,我认为这是一种有效的做法。

使用函数表达式可以修正上面的例子,这才是正确(也包括函数声明)创建构造函数的方式:

const Message = function(text) {
  this.text = text;
};
const helloMessage = new Message('Hello World!');
console.log(helloMessage.text); // => 'Hello World!'
复制代码

太短的语法

箭头函数有个很好的特点,就是可以省略参数的圆括号,块的花括号{}和 return (如果函数体只有一个语句),这可以让我们写很简短的函数。

我的大学编程教授给了学生一个有趣的任务:用 C 语言写一个最短的函数来计算字符串长度,这是学习和探索新语言的好方法。

然而,在实际应用中,代码会被很多开发者读到,最短的语法有时不适合让你的同事快速地理解函数。

在某种程度上,压缩函数会使得函数难以阅读,所以尽量不要只凭一时意气使用,看一个例子:

const multiply = (a, b) => b === undefined ? b => a * b : a * b;
const double = multiply(2);
double(3);      // => 6
multiply(2, 3); // => 6
复制代码

multiply 返回两个数字的乘法结果,或者绑定到第一个参数上的闭包(用于后面的乘法)。 函数正确执行并且很短,但一眼看过去却不易于理解。

为了提高可读性,可以从箭头函数中恢复可选的花括号和返回语句,或者使用常规函数:

function multiply(a, b) {
  if (b === undefined) {
    return function(b) {
      return a * b;
    }
  }
  return a * b;
}
const double = multiply(2);
double(3);      // => 6
multiply(2, 3); // => 6
复制代码

最好是在简短和冗长之间找到一个平衡点,这样可以使你的 JavaScript 更加直观。

5. 总结

毫无疑问,箭头函数是很棒的功能。如果正确使用,可以简化前面必须使用 .bind() 或尝试捕获上下文的地方,另外,它还简化了代码。

有些情况优点会给其他地方带来不利。当需要动态上下文时,就不能使用箭头函数:定义方法、使用构造函数创建对象、处理事件时从 this 中获取目标。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章