【问题标题】:Are arrow functions faster (more performant, lighter) than ordinary standalone function declaration in v8?箭头函数是否比 v8 中的普通独立函数声明更快(性能更高、更轻)?
【发布时间】:2017-05-17 16:47:25
【问题描述】:

我问这个问题是因为我和我的同事在编码风格上存在争议,因为他更喜欢箭头函数声明:

const sum = (a, b) => a + b;

而且我更喜欢老式的独立函数声明:

function sum(a, b) {
    return a + b;
}

我的观点是旧式代码更具可读性,您可以更清楚地区分函数和变量声明。他的观点是带有箭头函数的代码运行得更快。

当您使用旧式独立函数声明而不是箭头函数时,您是否了解实际的性能损失(在 v8 中)?惩罚真的存在吗?

【问题讨论】:

  • 我希望性能差异可以忽略不计,并且取决于供应商。函数体以同样的方式run。区别在于函数对象实例化(例如prototype 属性)和执行上下文实例化(例如接收器)。
  • @Jonasw 我的问题更具体。当其他问题未指定任何环境时,我只对 v8 细节感兴趣。我将编辑问题以使其更清楚。
  • ECMA6 提供的新方式只是旧方式的语法糖。主体以完全相同的方式执行,因此这些声明之间的性能没有变化。相反,正如@BenAston 所述,函数的实例化和上下文存在差异(例如,箭头函数this 不会改变上下文)。关于可读性,这也是一种习惯。箭头函数似乎更难以阅读,但也因为我们习惯于以旧方式阅读它们。可读性也是一种观点。
  • @Jonasw 我不太相信性能测试,因为 v8 优化器可以做一些棘手的事情。希望有v8背景的人能更深入的阐述一下这个问题。

标签: javascript node.js v8


【解决方案1】:

V8 开发人员在这里。箭头函数(大部分)只是常规函数声明的“语法糖”。没有性能差异。

【讨论】:

  • 箭头函数表达式不仅仅是语法糖。顺便说一句,他们基本上仍然创建Function 对象。我从未阅读过 ES6.0 规范。完全(仅限 ES3.0)所以我说的可能不准确,但是:调用由箭头函数表达式构造的 Function 在其执行上下文中导致 this 的值不同(通常被称为 @ 987654324@ 来自调用者作用域链的值)。
  • @Matheus 这意味着当箭头函数使用this 时,它需要创建一个闭包。没什么特别的。此外,我希望函数 object 的创建在大多数情况下都会被优化掉,因此不必创建 .prototype 对象也可能没有任何好处。
  • @Matheus:是的,this 周围存在语义差异,这就是我写“(大部分)”的原因。这个问题(和我的回答)的重点是没有性能差异——就像你说的,在引擎盖下它们是相同的 Function 对象,这就是为什么我认为称它们为“语法糖”是公平的. -- @Bergi:能够优化函数对象的创建是例外,而不是规则,不幸的是,因为 JavaScript 中有很多东西是可观察的。由于这个原因,闭包密集型程序往往内存效率很低。
  • 我不确定你在问什么——解释是一种执行方式。无论哪种方式,“没有性能差异”涵盖了它。
  • @NormanXu:箭头函数的实现并没有在底层使用.bind(),所以没有“原始函数”可以包装:箭头函数原创。
【解决方案2】:

以下说明:

  1. 先行会受到惩罚(无论是传统的还是胖子)
  2. Chrome 没有明显区别

function goFat() {
    for (var i = 0; i < 1000000; i++) {
        var v = ()=>{};
        v();
    }
}

function goTraditional() {
    for (var i = 0; i < 1000000; i++) {
        var v = function() {};
        v();
    }

}

function race() {
  var start = performance.now();
  goTraditional();
  console.log('Traditional elapsed: ' + (performance.now() - start));
  start = performance.now();
  goFat()
  console.log('Fat elapsed: ' + (performance.now() - start));
  start = performance.now();
  goTraditional();
  console.log('Traditional elapsed: ' + (performance.now() - start));
  start = performance.now();
  goFat()
  console.log('Fat elapsed: ' + (performance.now() - start));
  console.log('------');
}
&lt;button onclick="race()"&gt;RACE!&lt;/button&gt;

【讨论】:

  • 这是一个巨大的惩罚,因为你在点击按钮之前启动了第一个计时器......
  • 当你不知道 v8 的幕后发生了什么(我真的不知道我读过 v8 的源代码,但我不是专家)你不能由于 v8 优化,以正确的方式解释结果。查看这篇文章以获取一些示例mrale.ph/blog/2012/12/15/microbenchmarks-fairy-tale.html
  • 很公平,但是如果您不得不担心这一点,您不是已经回答了您的问题吗?如果在实践中无法检测到差异,那么询问理论是没有意义的(除非您对力学的差异感兴趣,这不是您所要求的)。
  • @BenAston 我只是希望一些具有深厚 v8 知识的大师能看到我的问题并给出客观的答案。 :)
  • 好的。该规范定义了创建这两种功能所采取的步骤。也可能有某种悬空“这会做得更多”的手指。
【解决方案3】:

我认为类属性中的箭头函数可能会导致一些性能问题。 这是一个例子:

class Car {
  setColor = (color) => { this.color = color; }

  constructor() {
     this.color = '';
     this.getColor = () => { return this.color; };
  }

  printCarColor() {
     console.log(this.color);
  }
}
var c = new Car();
console.log(c);

如果我们看一下变量 c,您会注意到函数 setColorgetColor为每个实例创建全新的,并且每个新副本都放置在每个实例上,而函数 printCarColor 驻留在原型上。

如果您希望一千个实例能够进行固定上下文方法引用,那么您将需要一千个单独的方法(不是共享的),当然,您将不得不存储每个实例本身上千种不同的方法,从而破坏了单个共享原型的全部意义。

【讨论】:

  • 我真的很想看到有人对此进行测试。
【解决方案4】:

nodejs有两个例子:

function testFat(a, b) {
    return a + b;
}

let testArrow = (a, b) => a + b;

let t1 = process.hrtime();
let tmp1 = 0;
for (let i = 0; i < 1000000000; ++i) {
    tmp1 = testFat(tmp1, i);
}
var fatTime = process.hrtime(t1);
console.log('fat', fatTime);

let t2 = process.hrtime();
let tmp2 = 0;
for (let i = 0; i < 1000000000; ++i) {
    tmp2 = testArrow(tmp2, i);
}
var arrowTime = process.hrtime(t2);
console.log('arrow', arrowTime);
function testFat() {
    return 0;
}

let testArrow = () => 0;

let t1 = process.hrtime();
for (let i = 0; i < 1000000000; ++i) {
    testFat();
}
var fatTime = process.hrtime(t1);
console.log('fat', fatTime);

let t2 = process.hrtime();
for (let i = 0; i < 1000000000; ++i) {
    testArrow();
}
var arrowTime = process.hrtime(t2);
console.log('arrow', arrowTime);```

结果是:

bash-3.2$ 节点 test_plus_i.js

胖 [0, 931986419]

箭头 [0, 960479009]

bash-3.2$ 节点 test_zero.js

胖 [0, 4795578​​88]

箭头 [0, 478563661]

bash-3.2$ 节点 --version

v12.8.0

bash-3.2$

所以你可以看到函数调用开销没有区别。

【讨论】:

  • 这是对这个问题的一个很好的回答。绑定 this 的开销可能很小而无法注意到。不过,它需要多次运行,并且还需要以相反的顺序运行测试循环。没有人知道 gc 背景正在发生什么并弄乱了结果..
【解决方案5】:

我在 jsben.ch 中做了一个简短的基准测试。我运行了很多次,似乎箭头函数在大多数情况下只比普通函数快一点点。即使一两次正常功能也更快......看起来差异微不足道。所以简而言之 - 如果您不需要介意 contextthis,只需使用看起来更适合您的东西;)

https://jsben.ch/kJxPT

const a = (b, c) => b+c;

a(1,2);

VS

function a(b,c){
    return b+c;
}

a(1,2);

【讨论】:

    【解决方案6】:

    在我的 exp 中,我发现箭头函数确实比普通的 JS 函数运行得更快。 这是 react 中的一个小 sn-p,它使用箭头和正常功能。我发现使用箭头函数的组件比使用普通js函数的组件运行速度要快一些。

    https://codepen.io/lokeshpathrabe/pen/qgzadx

    class Fun extends React.Component {
    
      constructor(props){
        super(props);
        this.state = {start: new Date().getTime(),
                     end: new Date().getTime(),
                     number: 0};
        console.log('Function start: ', this.state.start);
        const fun = function(me){
          let n = me.state.number
          me.setState({
            ...me.state, end: new Date().getTime(), number: ++n
          })
        }
        this.interval = setInterval(fun, 1, this);
      }
    
      stop(){
        clearInterval(this.interval);
      }
    
      componentDidUpdate(){
        if((this.state.end - this.state.start) > 5000){
          console.log('Function end: ', this.state.end);
          clearInterval(this.interval)
        }
      }
    
      render() {
        return (
          <div>
            <h2>Counter with Function {this.state.number}</h2>
          </div>
        )
      }
    }
    
    class Arrow extends React.Component {
    
      constructor(props){
        super(props);
        this.state = {start: new Date().getTime(),
                     end: new Date().getTime(),
                     number: 0};
        console.log('Arrow start: ', this.state.start);
        this.interval = setInterval(()=>{
          let n = this.state.number
          this.setState({
            ...this.state, end: new Date().getTime(), number: ++n
          })
        }, 1);
      }
    
      stop(){
        clearInterval(this.interval);
      }
    
      componentDidUpdate(){
        if((this.state.end - this.state.start) > 5000){
          console.log('Arrow end: ', this.state.end);
          clearInterval(this.interval)
        }
      }
    
      render() {
        return (
          <div>
            <h2>Counter with Arrow {this.state.number}</h2>
          </div>
        )
      }
    }
    
    class HOC extends React.Component {
    
      render() {
    
        return (<div>
            <h1>The one reaching higher count wins</h1>
            <Arrow/>
            <Fun/>
            </div>);
      }
    }
    
    ReactDOM.render(<HOC />, document.getElementById('react-content'))
    

    如果您的意见不同,请告诉我

    【讨论】:

      【解决方案7】:

      箭头函数只是一个函数表达式。以下是相等的:

      const foo = (a, b) => a + b // foo = an anonymous function
      const foo = function(a, b) { return a + b; }
      const foo = new Function("a", "b", "return a + b")
      

      可以提升函数声明:

      function foo(a, b) { return a + b; }
      

      箭头函数不能用作生成器函数,例如:

      function* foo(a, b) {
        yield a;
        yield b;
      }
      

      当您的函数需要 this 关键字时,请考虑使用它们。


      至少在性能上没有太大差异。

      【讨论】:

        猜你喜欢
        • 2015-11-05
        • 1970-01-01
        • 1970-01-01
        • 2019-02-24
        • 2014-08-17
        • 2019-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多