JavaScript中的胖箭头——ES6箭头函数介绍及应用实例

373 阅读9分钟

这是官方的!JavaScript中的胖箭头!

这是正式的!我们将得到一个新的函数语法! TC39小组(负责交付ES 6的小组)已经就JavaScript函数表达式的简略语法达成了共识。它被称为胖箭头语法,是基于CoffeeScript中的一个类似结构。

毫无疑问,我很高兴我们终于有了一个替代目前语法的不必要的笨重和繁琐的方法,但我无法摆脱一种纠结的感觉,即这个建议(以其目前的形式)有缺陷,以至于它实际上可能使新的开发者比他们更困惑。我将介绍一下这个新结构的主要特征,然后解释我的担忧以及如何减轻这些担忧。

在Java中终于有了胖箭头

Java会继续增加新的功能,并在适当的时候使自己对公众更加有用。也就是说,有很多人抱怨说,该服务从来不允许他们放入他们想放的胖箭头。这是程序中一个明显的漏洞,现在已经被取缔了。人们终于能够得到他们一直在寻找的胖箭头设计,而不必使用另一个程序来实现它。

确保你看一看 Java为你提供的全部范围,因为你探索的承诺,它将是比目前市场上其他任何东西都更宏伟的东西。如果可以的话,你会想利用这些新的箭头设计,而且你会想尽可能地检查一下Java的所有最新产品。

BS警报

在开始之前,我应该让你知道,我将对脂肪箭的工作原理做很多断言。我有理由相信他们中的大多数与最新的提议是一致的,但由于研究材料很少(我依靠ES Wiki和ES讨论列表),而且例子无法测试(traceur编译器还不支持脂肪箭头),所以会有 一些错误,对此我先道歉。我欢迎大家的指正,并会在得到指正后更新内容。谢谢!

它是如何工作的?

语法

胖箭头语法有以下特点:

1.箭头(=>)取代了函数关键词的位置

2.参数在箭头前指定,当有0个、2个或更多参数时,需要用括号。

3.当函数体由单一表达式组成时,块状语法(即用大括号括住函数体)是可选的,否则是必须的。

4.当函数体由单个表达式组成时,返回关键字是隐含的。在所有其他情况下,必须明确使用返回。

下面是 一些简单的例子。我把每个胖箭头的用例都与相应的长式语法配对,尽管我们将在后面看到配对的函数不一定代表相同的行为。为了让大家熟悉,我用var关键字来定义变量,但到ES6实现时,你更可能使用let,它允许用块范围来定义变量。

//empty function
var fat1 = () => {};
var long1 = function() {};

//return the square
var fat2 = x => x * x;
var long2 = function(x) {return x * x};

//add two numbers
var fat3 = (a, b) => a + b;
var long3 = function(a, b) {return a + b};

//return square root if x is a number, otherwise return x 
var fat4 = x => (typeof x == "number") ? Math.sqrt(x) : x;
var long4 = function(x) {
  return (typeof x == "number") ? Math.sqrt(x) : x;
};

肥大的箭头给功能性的JavaScript带来了一种简洁的优雅......

//return a new array containing the squares of the original...
[1, 2, 3, 4, 5].map(x => x * x); //[1, 4, 9, 16, 25]

//capitalize...
['caption', 'select', 'cite', 'article'].map(word => word.toUpperCase()); 
//['CAPTION', 'SELECT', 'CITE', 'ARTICLE']

//rewrite all instances of Fahrenheit as Celsius...
function f2c(x) {
  var test = /(\d+(\.\d*)?)F\b/g;
  return x.replace(test, (str, val) => (val-32)*5/9 + "C");
}
f2c("Store between 50F and 77F"); //"Store between 10C and 25C"

(最后一个例子是对这个传统实现的重写)。

胖箭头,没有多余的东西给你

胖箭头不仅使用了轻量级的语法,而且还生成了轻量级的函数...

没有构造器

使用 胖箭头语法创建的函数没有原型属性,这意味着它们不能作为构造函数使用。如果你试图使用一个胖箭头函数作为构造函数,它会抛出一个TypeError。

没有参数

在胖箭头函数的执行上下文中,参数的对象不可用。这不是一个巨大的损失;当ES 6全面展开的时候,我们可以预期参数已经被废弃,转而使用rest (..) 语法。

没有名字

函数表达式,就有命名的函数表达式。胖箭头函数没有名字的地方,所以它们永远只是普通的匿名函数表达式。

这个的价值

用胖箭头语法定义的函数有其上下文的约束;也就是说,这个值被设置为包围范围的值(如果存在外层函数,则为全局对象)。

//with long-form inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var longInner = function() {
      console.log(this); //this is global object
    };
    longInner(); 
  }
}

myObj.longOuter();

//with fat arrow inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var fatInner = () => 
      console.log(this); //this is myObj
    fatInner(); 
  }
}

myObj.longOuter();

这是一种硬性绑定,这意味着如果一个胖箭头被用来定义一个对象字面的方法,那么即使从一个借用对象中调用,它也将继续被绑定到该对象。

var myObj = {
  myMethod: function() {return () => this;},
  toString: () => "myObj" 
}

var yourThievingObject = {
  hoard: myObj.myMethod,
  toString: () => "yourThievingObject"
};

yourThievingObject.hoard(); //"myObj"

同样,一个胖箭头函数的这个值也不能通过调用或应用的方式来修改。

//traditional long inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var longInner = function() {
      console.log(this); //this is now myOtherObj
    }
    longInner.call(myOtherObj); 
  }
}

myOtherObj = {};
myObj.longOuter();

//new fat inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var fatInner = () => 
      console.log(this); //this is still myObj
    fatInner.call(myOtherObj); 
  }
}

myOtherObj = {};
myObj.longOuter();

那么问题出在哪里?

如果你浏览一下Stack Overflow的JavaScript部分,你会发现有几十个 问题,这些问题都是由困惑的开发者提出他们弄清楚JavaScript的这个分配过程有点儿复杂。

那么......还记得有五种方法可以在一个函数中定义这个值吗?

函数调用的语法这个的值
1.方法调用:
myObject.foo()
myObject
2.无基础的函数调用:
foo()
全局对象(如窗口)
(在严格模式下未定义)。
3.使用调用:
foo.call(context, myArg)
上下文
4.使用应用:
foo.apply(context, [myArgs])
上下文
5.使用new的构造函数:
var newFoo = new Foo();
新的实例
(例如:newFoo)

...好了,现在有了第六个...

函数调用的语法这个的值
6.Fat Arrow:
(x => x*x)()
词法父的这个

(还提出了第七条规则--将胖箭头的第一个参数命名为 "this "会将上下文与方法调用的基础引用绑定在一起--但幸好这个选项被推迟了)。

我很欣赏这种词法绑定背后的原理。它很直观,而且如果JavaScript要重新开始,这也不失为一个好办法。但是在这一点上,我已经爱上了动态值;它们使函数变得非常灵活,并且是对函数模式的一个很好的补充,其中函数构成了数据的基石,而其他对象仅仅是可替代物。

此外,如果新的开发者已经对JavaScript的任意分配上下文感到气馁,那么另一条规则可能足以让他们永远地结束。请记住,胖箭头是糖,而且是非常美味的糖;在第六定律的后果还没来得及沉淀的时候,它就会被许多开发者急切地吞噬。

目前的建议还有一个相关的问题。遗留函数(第三方或其他)通常假定它们的函数参数具有动态的这个值。这使得他们能够在任何给定的上下文中调用函数参数,除此之外,这也是一种添加混杂元素的有用方法。

Function.prototype.bind确实已经提供了一种硬绑定的形式,但它是明确地这样做的;另一方面,fat arrow的硬绑定是一种副作用,它是否会破坏这样的代码一点都不明显:

function mixin(obj, fn) {
  fn.call(obj);
}

//long form function mixin is dynamically bound
var withCircleUtilsLong = function() {
  this.area = function() {return this.radius * this.radius * Math.PI};
  this.diameter = function() {return this.radius + this.radius};
}

//fat arrow function mixin is lexically bound (to global object in this case)
var withCircleUtilsFat = () => {
  this.area = function() {return this.radius * this.radius * Math.PI};
  this.diameter = function() {return this.radius + this.radius};
}

var CircularThing = function(r) {this.radius = r};

//utils get added to CircularThing.prototype
mixin(CircularThing.prototype, withCircleUtilsLong); 
(new CircularThing(1)).area(); //3.14

//utils get added to global object
mixin(CircularThing.prototype, withCircleUtilsFat); 
(new CircularThing(1)).area(); //area is undefined

如何修复它

好了,抱怨够了;是时候提出一些建议了。这里有三个想法可以消除,或者至少减轻新的胖箭头上下文行为的负面影响:

1)(这个很简单)。让胖箭头函数以与任何常规函数表达式相同的方式来定义它--即根据上表中的五条规则。值得注意的是,CoffeeScript定义了fat arrow作为他们的thin arrow(->)语法的替代。在CoffeeScript中,细箭头的行为方式大体上与常规的JavaScript函数表达式相同。相比之下,ES6的胖箭头试图同时做两件事--成为语法的唯一缩写者和重新定义上下文分配。如果只做其中一件,或者另一件,就不会那么混乱了。

2)(你可能也看到了这一点)。同时引入细箭头语法。这样一来,开发者就会被吸引到更安全、不那么激进的糖,即简单地缩写他们的函数表达式,而不会抛出扰乱上下文的秘密惊喜。胖箭头表达式成为特殊情况,而不是默认情况。这封邮件认为胖箭头和瘦箭头之间的区别会使人们感到困惑,但通过删除瘦箭头,我们删除了动态绑定的长形式函数和硬绑定的短形式函数之间的垫脚石,必要的概念飞跃变得更加激进。

3)(这个建议是由@fb55es讨论列表中提出的)。只有在没有其他绑定建议的情况下,才采用词法范围作为退路。换句话说,这将接受方法调用中的基础引用的值,或者通过调用或应用传递的上下文,但当作为一个独立的函数被调用时,将推迟到词法范围。(独立函数可能是JavaScript中唯一真正需要修正的部分)。

总结

箭头函数的主要目标是简洁?还是硬性的词汇绑定?如果是前者(即使不是,很多开发者也会认为是),那么我们就应该注意不要用新的或令人惊讶的行为来超载它。