JavaScript中的多个箭头函数是什么意思?

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

handleChange = field => e => {
e.preventDefault();
/// Do something here
}
162041 次浏览

可以这样想,每次看到箭头时,都将其替换为function
function parameters在箭头之前定义。
所以在你的例子中:

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

然后一起:

function (field) {
return function (e) {
e.preventDefault();
};
}

从文档里

// 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

了解箭头函数的可用语法将让您了解他们在“链接”时引入的行为,就像您提供的示例中一样。

当箭头函数编写时没有块括号,有或没有多个参数,构成函数主体的表达式将返回隐含。在您的示例中,该表达式是另一个箭头函数。

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上的“箭头函数”

函数表达式相比,箭头函数表达式的语法更短,并且在词法上绑定这个值。箭头函数始终是匿名

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

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)             |  }

这是一个柯里化函数

首先,用两个参数检查这个函数…

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

这里又是咖喱状的…

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

这是相同的1代码,没有箭头函数…

const add = function (x) {
return function (y) {
return x + y
}
}

专注于return

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

const f = someParam => returnValue

So our add function returns a function – we can use parentheses for added clarity. The bolded text is the return value of our function add

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

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

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

调用柯里化函数

因此,为了使用我们的柯里化函数,我们必须以不同的方式调用它…

add(2)(3)  // returns 5

这是因为第一个(外部)函数调用返回第二个(内部)函数。只有在我们调用第二个函数后,我们才能真正得到结果。如果我们将调用分隔在两行上,这一点就更明显了…

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

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

相关:"绑定、部分应用和柯里化有什么区别?"

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

handleChange = field => e => {
e.preventDefault()
/// Do something here
}

我们将从不使用箭头函数来表示它开始…

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

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

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

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

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


更多的箭

如果需要,可以对两个以上的箭头函数进行排序-

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

柯里化函数能够带来令人惊讶的事情。下面我们看到$定义为具有两个参数的柯里化函数,但在调用站点,似乎我们可以提供任意数量的参数。柯里化是的抽象-

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

部分适用

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

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的工作演示,您可以在自己的浏览器中玩-

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))
<input name="foo" placeholder="type here to see ascii codes" size="50">

一般提示:如果您对任何新的JavaScript语法及其编译方式感到困惑,您可以检查巴别塔。例如,在Babel中复制代码并选择ES 2015预设将给出如下输出

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

Babel

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

箭头函数在词法上绑定this,即它们没有自己的this参数,而是从封闭范围中获取this

上述代码的等价物将是

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

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

因此,不是直接绑定外部函数,而是将其绑定在类构造函数中

class Something{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(field) {
return function(e) {
e.preventDefault();
// do something
}
}
}

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

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

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

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

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

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

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

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

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

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

简要

它是一个返回另一个以简短方式编写的函数的函数。

const handleChange = field => e => {
e.preventDefault()
// Do something here
}


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

为啥?

你是否曾经遇到过需要创建一个可以自定义的函数的情况?或者你有一个带有固定参数的回调函数,但是你需要在避免全局变量的同时发送额外的变量?如果你回答是的,那么这就是如何做到这一点。

例如,我们有一个带有onClick回调的按钮。我们想将id传递给函数,但是,onClick只接受一个参数event,所以我们不能这样做:

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

这是行不通的!

这里作为一个解决方案,我们编写一个函数,返回另一个在其变量范围内具有id的函数,而不使用任何全局变量:

const handleClick = id => event {
event.preventDefault()
// Dispatch some delete action by passing record `id`
}


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


功能组成

多个箭头函数也称为“咖喱函数”,它们用于函数组合。

// It is just an example, unfortunately, redux does not export dispatch function
import {dispatch, compose} from 'redux'


const pickSelectedUser = props => {
const {selectedName, users} = props
const foundUser = users.find(user => user.name === selectedName)
  

return foundUser.id
}


const deleteUser = userId => event => {
event.preventDefault()
dispatch({
type: `DELETE_USER`,
userId,
})
}


// The compose function creates a new function that accepts a parameter.
// The parameter will be passed throw the functions from down to top.
// Each function will change the value and pass it to the next function
// By changing value it was not meant a mutation
const handleClick = compose(
deleteUser,
pickSelectedUser,
)


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

这可能不完全相关,但由于提到的问题反应用例(我一直遇到这个SO线程):双箭头函数的一个重要方面在这里没有明确提到。 只有“第一个”箭头(函数)被命名(因此可以通过运行时“区分”),任何后续箭头都是匿名的,并且从React的角度来看,每个渲染都被视为“新”对象。

因此,双箭头函数将导致任何PureComponent一直重新渲染。

示例

您有一个带有更改处理程序的父组件:

handleChange = task => event => { ... operations which uses both task and event... };

并使用类似的渲染:

{ tasks.map更改任务this.handle

然后在输入或单击上使用handleChange。这一切都有效,看起来非常漂亮。但这意味着任何导致父级重新渲染的更改(比如完全不相关的状态更改)也将重新渲染你所有的My任务,即使它们是PureComponents。

这可以通过多种方式来缓解,例如传递“最外”箭头和您将为其提供的对象,或编写自定义ShowdUpdate函数或回到基础知识,例如编写命名函数(并手动绑定this…)