关于ecmascript 6:多个箭头函数在javascript中意味着什么?

What do multiple arrow functions mean in javascript?

我一直在阅读一堆react代码,我看到这样的东西,我不明白:

1
2
3
4
handleChange = field => e => {
  e.preventDefault();
  /// Do something here
}


这是一个咖喱的功能

首先,使用两个参数检查此函数

1
2
const add = (x, y) => x + y
add(2, 3) //=> 5

在这里,它再次以咖喱形式出现

1
const add = x => y => x + y

这是没有箭头功能的相同1代码

1
2
3
4
5
const add = function (x) {
  return function (y) {
    return x + y
  }
}

专注于return

以另一种方式可视化它可能会有所帮助。我们知道箭头函数的工作方式是这样的 - 让我们特别注意返回值。

1
const f = someParam => returnValue

所以我们的add函数返回一个函数 - 我们可以使用括号来增加清晰度。粗体文本是函数add的返回值

1
const add = x => (y => x + y)

换句话说,某些数字的add返回一个函数

1
add(2) // returns (y => 2 + y)

调用curried函数

因此,为了使用我们的curried函数,我们必须稍微改变它

1
add(2)(3)  // returns 5

这是因为第一个(外部)函数调用返回第二个(内部)函数。只有在我们调用第二个函数之后才能实际获得结果。如果我们将呼叫分成两行,这一点就更明显了

1
2
const add2 = add(2) // returns function(y) { return 2 + y }
add2(3)             // returns 5

将我们的新理解应用于您的代码

related:"What’s the difference between binding, partial application, and currying?"

好了,现在我们了解它是如何工作的,让我们来看看你的代码

1
2
3
4
handleChange = field => e => {
  e.preventDefault()
  /// Do something here
}

我们首先在不使用箭头功能的情况下进行表示

1
2
3
4
5
6
7
handleChange = function(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
    // return ...
  };
};

但是,因为箭头函数在词法上绑定this,它实际上看起来更像是这样

1
2
3
4
5
6
7
handleChange = function(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
    // return ...
  }.bind(this)
}.bind(this)

也许现在我们可以更清楚地看到这一点。 handleChange函数正在为指定的field创建函数。这是一种方便的React技术,因为您需要在每个输入上设置自己的侦听器才能更新应用程序状态。通过使用handleChange函数,我们可以消除导致为每个字段设置change侦听器的所有重复代码。凉!

1这里我没有词法绑定this因为原始的add函数不使用任何上下文,所以在这种情况下保留它并不重要。

更多的箭头

如有必要,可以对两个以上的箭头功能进行排序 -

1
2
3
4
5
6
7
8
9
const three = a => b => c =>
  a + b + c

const four = a => b => c => d =>
  a + b + c + d

three (1) (2) (3) // 6

four (1) (2) (3) (4) // 10

Curried函数能够令人惊讶。下面我们看到$定义为带有两个参数的curried函数,但在调用站点,看起来好像我们可以提供任意数量的参数。 Currying是arity的抽象 -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const $ = x => k =>
  $ (k (x))
 
const add = x => y =>
  x + y

const mult = x => y =>
  x * y
 
$ (1)           // 1
  (add (2))     // + 2 = 3
  (mult (6))    // * 6 = 18
  (console.log) // 18
 
$ (7)            // 7
  (add (1))      // + 1 = 8
  (mult (8))     // * 8 = 64
  (mult (2))     // * 2 = 128
  (mult (2))     // * 2 = 256
  (console.log)  // 256

部分申请

部分应用是一个相关的概念。它允许我们部分应用函数,类似于currying,除了函数不必以curry形式定义 -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const partial = (f, ...a) => (...b) =>
  f (...a, ...b)

const add3 = (x, y, z) =>
  x + y + z

partial (add3) (1, 2, 3)   // 6

partial (add3, 1) (2, 3)   // 6

partial (add3, 1, 2) (3)   // 6

partial (add3, 1, 2, 3) () // 6

partial (add3, 1, 1, 1, 1) (1, 1, 1, 1, 1) // 3

这是你可以在自己的浏览器中玩的partial的工作演示 -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const partial = (f, ...a) => (...b) =>
  f (...a, ...b)
 
const preventDefault = (f, event) =>
  ( event .preventDefault ()
  , f (event)
  )
 
const logKeypress = event =>
  console .log (event.which)
 
document
  .querySelector ('input[name=foo]')
  .addEventListener ('keydown', partial (preventDefault, logKeypress))
1
<input name="foo" placeholder="type here to see ascii codes" size="50">


理解箭头函数的可用语法将使您了解它们在您提供的示例中"链接"时所引入的行为。

如果在没有块括号的情况下编写箭头函数(包含或不包含多个参数),则会隐式返回构成函数体的表达式。在您的示例中,该表达式是另一个箭头函数。

1
2
3
4
5
6
7
No arrow funcs              Implicitly return `e=>{}`    Explicitly return `e=>{}`
---------------------------------------------------------------------------------
function (field) {         |  field => e => {            |  field => {
  return function (e) {    |                             |    return e => {
      e.preventDefault()   |    e.preventDefault()       |      e.preventDefault()
  }                        |                             |    }
}                          |  }                          |  }

使用箭头语法编写匿名函数的另一个优点是它们在词法上与它们的定义范围绑定。来自MDN上的'箭头功能':

An arrow function expression has a shorter syntax compared to function expressions and lexically binds the this value. Arrow functions are always anonymous.

考虑到它来自reactjs应用程序,这在您的示例中尤为重要。正如@naomik所指出的,在React中,您经常使用this访问组件的成员函数。例如:

1
2
3
4
5
6
7
Unbound                     Explicitly bound            Implicitly bound
------------------------------------------------------------------------------
function (field) {         |  function (field) {       |  field => e => {
  return function (e) {    |    return function (e) {  |    
      this.setState(...)   |      this.setState(...)   |    this.setState(...)
  }                        |    }.bind(this)           |    
}                          |  }.bind(this)             |  }


一般提示,如果您对任何新的JS语法以及如何编译它感到困惑,您可以检查babel。例如,在babel中复制代码并选择es2015预设将得到这样的输出

1
2
3
4
5
6
handleChange = function handleChange(field) {
 return function (e) {
 e.preventDefault();
  // Do something here
   };
 };

babel


想象一下,每次看到箭头时,都要用function替换它。function parameters在箭头之前定义。
所以在你的例子中:

1
2
field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}

然后在一起:

1
2
3
4
5
function (field) {
    return function (e) {
        e.preventDefault();
    };
}

来自文档:

1
2
3
4
5
6
7
8
// Basic syntax:
(param1, param2, paramN) => { statements }
(param1, param2, paramN) => expression
   // equivalent to:  => { return expression; }

// Parentheses are optional when there's only one argument:
singleParam => { statements }
singleParam => expression


简单而简单?

它是一个返回另一个简短写入函数的函数。

1
2
3
4
5
6
7
8
9
10
11
12
const handleChange = field => e => {
  e.preventDefault()
  // Do something here
}

// is equal to
function handleChange(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
  }
}

为什么人们这样做?

您是否需要编写可自定义的功能?
或者你必须编写一个具有固定参数(参数)的回调函数,
但是你需要将更多变量传递给函数但是要避免全局变量吗?
如果你的答案是肯定的,那就是如何做到的。

例如,我们有一个带有onClick回调的button。我们需要将id传递给函数,但是onClick只接受一个参数event,我们不能像这样传递额外的参数:

1
2
3
4
const handleClick = (event, id) {
  event.preventDefault()
  // Dispatch some delete action by passing record id
}

不起作用!

因此,我们创建一个函数,它将返回具有自己的变量范围的其他函数,而不需要任何全局变量,因为全局变量是邪恶的。

函数handleClick(props.id)}下面将被调用并返回一个函数,其范围内将包含id!无论按下多少次,ids都不会相互影响或改变,它们是完全孤立的。

1
2
3
4
5
6
7
8
9
10
11
12
13
const handleClick = id => event {
  event.preventDefault()
  // Dispatch some delete action by passing record id
}

const Confirm = props => (
 
    Are you sure to delete?
    <button onClick={handleClick(props.id)}>
      Delete
    </button>
  </div
)

您的问题中的示例是curried function的示例,其使用arrow function并且第一个参数具有implicit return

箭头函数通过词法绑定它,即它们没有自己的this参数,但是从封闭范围中取出this

相当于上面的代码

1
2
3
4
5
6
const handleChange = (field) {
  return function(e) {
     e.preventDefault();
     /// Do something here
  }.bind(this);
}.bind(this);

关于您的示例还有一点需要注意的是将handleChange定义为const或函数。可能你正在使用它作为类方法的一部分,它使用class fields syntax

所以不是直接绑定外部函数,而是将它绑定在类构造函数中

1
2
3
4
5
6
7
8
9
10
11
12
class Something{
    constructor(props) {
       super(props);
       this.handleChange = this.handleChange.bind(this);
    }
    handleChange(field) {
        return function(e) {
           e.preventDefault();
           // do something
        }
    }
}

在示例中要注意的另一件事是隐式和显式返回之间的区别。

1
const abc = (field) => field * 2;

以上是隐式回报的一个例子,即。它将value字段作为参数并返回结果field*2,它显式指定要返回的函数

对于显式返回,您将明确告诉方法返回值

1
const abc = () => { return field*2; }

关于箭头函数的另一个注意事项是它们没有自己的arguments,但也从父类作用域继承它。

例如,如果你只是定义一个箭头函数

1
2
3
const handleChange = () => {
   console.log(arguments) // would give an error on running since arguments in undefined
}

作为替代箭头,函数提供了您可以使用的其余参数

1
2
3
const handleChange = (...args) => {
   console.log(args);
}


1
2
3
4
 var handleChange = field => e => {
  e.preventDefault();
  /// Do something here
 }

在Ecma5中,翻译:

1
2
3
4
5
6
7
8
9
10
11
12
13
"use strict";

 var handleChange = function handleChange(field) {
   return function (e) {
     e.preventDefault(); /// Do something here
   };
 };

 var f = function(x,y) { return x+y }
 var g = function(x) { return function(y) { return x+y }}

 f: (T x T) -> T
 g: T -> T -> T

T:泛型

它会改变函数的类型,但结果没有。