javascript中的多个箭头功能是什么意思?


472

我一直在阅读一堆react代码,并且看到类似我不明白的东西:

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

11
只是为了好玩,凯尔·辛普森Kyle Simpson)将所有箭头决策路径放入此流程图。来源:在Mozilla Hacks博客文章《ES6深度:箭头功能
gfullam

由于有很好的答案,现在赏金很大。您能否详细说明以下答案未涵盖的内容?
迈克尔·沃纳

5
箭头功能流程图的URL现在已断开,因为该书有新版本。工作URL是raw.githubusercontent.com/getify/You-Dont-Know-JS/1st-ed/...
DHIRAJ古普塔

Answers:


831

那是一个咖喱函数

首先,使用两个参数检查此功能……

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

因此,我们的add函数返回一个函数 –我们可以使用括号来增加清晰度。该粗体文字是我们的函数的返回值add

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

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

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

调用咖喱函数

因此,为了使用我们的curried函数,我们必须对其进行一些不同的调用……

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 ...
  };
};

但是,由于arrow函数按词法绑定了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

部分申请

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

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


2
这太好了!有人实际分配“ $”的频率是多少?还是反应中的别名?请原谅我的无知,只是感到好奇,因为我看不到其他语言中的符号获得分配的次数过多。
Caperneoignis

7
@Caperneoignis $用于演示该概念,但是您可以根据需要命名。巧合的是,却完全不相关,$ 在流行的库(如jQuery)中使用,其中$是整个函数库的全局入口点。我认为它也已被其他人使用。您会看到的另一个是_,在下划线和lodash之类的库中流行。没有一个符号比另一个更有意义。指定的含义为您的程序。这只是有效的JavaScript:D
谢谢您,

1
圣洁的frijoli,很好的答案。希望操作会接受
麦迪逊

2
@Blake $通过查看其用法,可以更好地了解它。如果您要询问实现本身,$则是一个函数,该函数接收一个值x并返回一个新函数k => ...。查看返回的函数的主体,k (x)我们知道k也必须是一个函数,并且无论将什么结果k (x)放回到中$ (...),我们都知道会返回另一个k => ...,然后继续...如果您仍然卡住,让我知道。
谢谢您,

2
而此答案说明了此技术的工作原理以及存在的模式。我觉得对于在任何情况下这实际上都是更好的解决方案,没有任何具体说明。在什么情况下,abc(1,2,3)都不如理想abc(1)(2)(3)。很难对代码的逻辑进行推理,也很难读取函数abc,也很难读取函数调用。在您只需要知道abc的功能之前,现在您不确定abc返回的是哪些未命名的函数,然后执行两次。
穆罕默德·乌默尔

57

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

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

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指出的那样,在你做出反应经常访问组件的成员函数使用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)             |  }

53

一般提示,如果您对任何新的JS语法及其编译方式感到困惑,则可以检查babel。例如,将您的代码复制到babel中并选择es2015预设将给出类似的输出

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

巴别塔


42

像这样想,每次看到箭头时,都用替换它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

6
不要忘了提及词汇上的约束this
谢谢您

30

简洁 simple

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

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

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

人们为什么这样做

在需要编写可以自定义的功能时遇到了什么?或者您必须编写一个具有固定参数(参数)的回调函数,但是您需要向该函数传递更多变量,但要避免使用全局变量?如果您的回答为“ ”,那么这就是方法。

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

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

不起作用!

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

下面的函数handleClick(props.id)}将被调用并返回一个函数,它将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
)

2

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

箭头函数按词法绑定此对象,即它们没有自己的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;

上面是隐式返回的示例,即。它以值字段作为参数,并返回结果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);
}

1

它可能并不完全相关,但是由于提到的问题react使用案例(并且我一直碰到这个SO线程):双箭头功能的一个重要方面在这里没有明确提及。只有“第一个”箭头(函数)被命名(因此在运行时是“可区分的”),随后的所有箭头都是匿名的,从React的角度来看,每个箭头都被视为“新”对象。

因此,双箭头功能将导致任何PureComponent始终重新渲染。

您有一个带有更改处理程序的父组件,如下所示:

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

并使用如下渲染:

{ tasks.map(task => <MyTask handleChange={this.handleChange(task)}/> }

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

可以通过多种方式来缓解这种情况,例如传递“最外”箭头和您将使用的对象,或者编写自定义的shouldUpdate函数,或者回到基础知识,例如编写命名函数(并手动绑定此函数……)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.