绑定函数与箭头函数(在JavaScript中,或者对于react onClick事件)的区别

56

所以我正在尝试学习JavaScript和/或react,在理解构造函数中的.bind(this)时有些混淆。然而,我现在认为已经理解了它,只想知道为什么有人会在JavaScript中使用绑定而不是箭头函数(或在onClick事件中)。

使用这两种方法是否有优缺点?

请参见下面的代码示例。

绑定方法确保clickEvent函数中的this引用类:

class Click extends react.Component {
  constructor(props) {
    super(props)
    this.clickEvent = this.clickEvent.bind(this);
  }

  render = () => (
    <button onClick= { this.clickEvent } > Click Me < /button>
  )

  clickEvent() { console.log(this) } // 'this' refers to the class
}

然而以下方法也引用了该类:

class Click extends react.Component {

  render = () => (
    <button onClick= {() => { this.clickEvent() }}> Click Me < /button>
  )

  clickEvent() { console.log(this) } // 'this' refers to the class
}

箭头函数是 ES6 的一部分。如果您不使用 ES6,则必须绑定 this。这是我能想到的两者之间的一个区别。 - Pat Mellon
1
() => {this.clickEvent()} - Yury Tarabanko
2个回答

69

首先,让我们从每种技术的示例开始!
但是区别更多地与JavaScript语言本身有关。

绑定:

import React from 'react';

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.clickHandler = this.clickHandler.bind(this);
  }

  clickHandler() {
    console.log( this )
  }

  render() {
    return <button onClick={this.clickHandler}>Click Me</button>
  }
}

箭头函数:

import React from 'react';

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
  }

  clickHandler = () => {
    console.log( this )
  }

  render() {
    return <button onClick={this.clickHandler}>Click Me</button>
  }
}

优缺点:

在公共类字段上使用箭头函数更易读,因为代码行数较少, 但请记住,使用箭头函数可能会影响三件事:

首先是内存和性能;当您使用类字段定义函数时,整个方法驻留在每个类的实例上,而不是原型上,但使用bind技术,只有一个小的callback存储在每个实例上,调用存储在原型上的方法。

第二个可能受到影响的是您编写单元测试的方式。 您将无法使用组件原型来存根函数调用,如下所示:

const spy = jest.spyOn(MyComponent.prototype, 'clickHandler');
// ...
expect(spy).toHaveBeenCalled();

要想存根该方法,必须找到另一种方法,例如通过在props中传递虚拟回调函数或检查state更改。

最后是性能问题; 取决于您的脚本运行的JavaScript引擎, 使用超过某个限制的内存可能导致几乎随机的运行时挂起 (也称逻辑操作的延迟), 即使系统尚未低于内存,但除此之外,逻辑的运行时性能对于两种技术应该是相同的。
请参见下面“其他工具”部分以获取解决方法。

结论

计算机非常擅长阅读代码;您不必担心。 您可以考虑使用类属性箭头函数使您的代码更易于阅读。


其他工具:

如果您想保持人类可读性和性能,请考虑使用plugin-transform-arrow-functions插件,只需运行npm i --save-dev @babel/plugin-transform-arrow-functions并将其添加到您的"babel.config.js"或".babelrc"文件中,例如:

{
  "presets": ["module:metro-react-native-babel-preset"],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": false }],
    ["@babel/plugin-transform-arrow-functions", { "spec": true }]
  ]
}

"spec": true 表示插件会自动将箭头函数转换为方法/函数绑定(位于 prototype 中)。

此外,似乎该插件与 React Native 兼容(至少在链接帖子中提到的版本中)。

或者,您可以使用类似 auto-bind 装饰器 的东西,将上面的示例转换为:

import React from 'react';

import { boundMethod as bind } from 'autobind-decorator';

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
  }

  @bind
  clickHandler() {
    console.log( this )
  }

  render() {
    return <button onClick={this.clickHandler}>Click Me</button>
  }
}

请注意,不需要在每个函数上都放置 @bind。您只需要绑定传递的函数。例如:onClick={this.doSomething} 或者 fetch.then(this.handleDone)

@Hacker 如果箭头函数在构造函数内部,它将隐藏IDE的自动完成功能,否则我不知道有什么区别。 - Top-Master

34

你的第二个例子会在每次 render 时重新创建封装函数。而在第一个例子中,你只在构造函数中创建一次原型函数,并为每个实例创建一次绑定函数。

作为另一种选择,你可以在构造函数中将处理程序创建为箭头函数:

class Click extends React.Component {
    constructor(props) {
        super(props);
        this.clickEvent = () => {   // ***
            console.log(this);      // ***
        };                          // ***
    }

    render = () => (
        <button onClick={this.clickEvent}>Click Me</button>
    );
}

是否使用bind还是另一种风格的问题。(我使用bind,这样函数就在原型上,我们可以对其进行模拟测试等。)

如果你正在使用类字段提案语法(它已经在大多数React项目的转译设置中启用,并且你正在使用它的render函数),你可以像这样编写它:

class Click extends React.Component {
    constructor(props) {
        super(props);
    }

    clickEvent = () => {    // ***
        console.log(this);  // ***
    };                      // ***

    render = () => (
        <button onClick={this.clickEvent}>Click Me</button>
    );
}

这其实是同一件事情。为每个实例创建一个单独的clickEvent函数,该函数将封闭实例。上面的两个示例做的事情完全相同(在构造函数中调用super()后创建函数并将其赋值给实例),唯一的区别是语法。


顺便说一句:你正在为类的每个实例创建一个单独的render函数。没有必要这样做,可以放在原型上。所以:

class Click extends React.Component {
    constructor(props) {
        super(props);
    }

    clickEvent = () => {
        console.log(this);
    };

    render() {
        return <button onClick={this.clickEvent}>Click Me</button>;
    }
}

男士,构造函数内和外部的箭头函数之间有类别上的区别吗?在您的第一个示例中,箭头函数位于构造函数中,但不在原型中。 - HackerMF
@HackerMF - 不,这只是语法上的差异,这就是为什么我说“这是一样的东西”。正如你所说,两者都不在原型上,而是每个实例都创建。使用公共类字段的版本将在箭头函数示例中完全相同方式创建clickEvent(并且在构造函数中调用super()后立即创建)。这就是为什么我在结尾处包含了一个小部分,展示如何将render放在原型上。但我应该回去展示如何将clickEvent也放在原型上(然后在构造函数中绑定它)。 - T.J. Crowder
啊,我没有做bind的例子,因为它已经在问题中了。 :-) - T.J. Crowder
对我来说,函数声明的绑定很清楚。我想与您确切地讨论箭头函数。 ;) - HackerMF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接