【问题标题】:Does spread operator affect performance?扩展运算符会影响性能吗?
【发布时间】:2019-09-14 12:30:03
【问题描述】:

我正在考虑以下两种构建对象数组的方法:

方法 1(列出所有属性,即使在对象之间重复):

const employees = [
  {
    company: 'ABC',
    country: 'IN',
    zip: 123,
    employeeId: 123,
    employeeName: 'p'
  },
  {
    company: 'ABC',
    country: 'IN',
    zip: 123,
    employeeId: 456,
    employeeName: 'q'
  },
  {
    company: 'ABC',
    country: 'IN',
    zip: 123,
    employeeId: 789,
    employeeName: 'r'
  }
];

方法2(避免与扩展运算符重复):

const commonParams = {
  company: 'ABC',
  country: 'IN',
  zip: 123
};

const employees = [
  {
    ...commonParams,
    employeeId: 123,
    employeeName: 'p'
  },
  {
    ...commonParams,
    employeeId: 456,
    employeeName: 'q'
  },
  {
    ...commonParams,
    employeeId: 789,
    employeeName: 'r'
  }
]

方法 2 更简洁,添加一个对所有数组元素都通用的新属性会容易得多(并且不易出错)。

但是,对于较大的 commonParams 对象,与方法 1 相比,方法 2(使用 扩展运算符)会影响性能吗?

扩展运算符是否会针对employees 数组中的每个对象循环遍历commonParams 对象的每个属性?

【问题讨论】:

标签: javascript arrays node.js


【解决方案1】:

是的,将引用一个对象的变量传播到另一个对象需要解释器查找变量引用的内容,然后查找传播的对象的所有可枚举自身属性(和相关值),所以插入到新对象中。这确实需要的处理能力。

但是,在现代计算机和现代 JS 引擎上,所需的处理能力几乎为零;当每秒可以处理数百万条指令时,这有什么关系?少量键值对无需担心。

除非您已经确定要使用 键值对来传播对象,并且它实际上会导致性能瓶颈,否则会更好避免过早优化的想法,并旨在编写干净、可读的代码(这很可能经常使用扩展语法调用)。对于较大的employees 数组,第二种方法比第一种更具可读性。

(不过,您也可以考虑使用.map,以使代码保持干燥:)

const employeesInitial = [
  {
    employeeId: 123,
    employeeName: 'p'
  },
  {
    employeeId: 456,
    employeeName: 'q'
  },
  {
    employeeId: 789,
    employeeName: 'r'
  }
];
const employees = employeesInitial.map((obj) => ({ ...obj, ...commonParams }));

【讨论】:

  • 谨防将 spread 与 array.reduce() 一起使用。我怀疑它会导致 O(n^2) 行为或更糟。使用大小为 2000 的数组,以下代码在我的机器上占用 7 秒以上: let phoneBook=inputs.reduce((acc,entry) => { let [name,phone] = entry.trim().split(' ' ); return {...acc, [name]:phone}; },{});
  • 而使用以下命令需要 0.07 秒(100 倍差异):let phoneBook=inputs.reduce((acc,entry) => { let [name,phone] = entry.trim(). split(' '); acc[name] = phone; return acc; },{});
  • 是的,您的第一个代码是O(n^2) - 循环内的循环可能会导致问题。
  • 请注意,我相信...obj, ...commonParams 意味着在参数冲突中,普通的会获胜。而在 OP 中,他将...commonParms 放在首位,因此任何特定设置都会覆盖它们。
【解决方案2】:

传播成本很高。我们在这里说的是 2 个数量级。

const { x, y } = z

z = { x, y: y + 1 } // faster
z = { ...z, y: y + 1 } // slower

虽然它们都完成了相似的事情,但它们的性能特征却截然不同。但这将取决于您的 JavaScript 是否以及如何被转译。

例如,如果您以 ES2015 为目标,Babel 实际上会发出类似于较快变体的内容,但如果您以 ES2017 为目标,您将按原样获得较慢的变体。如果您使用 Google Closure Compiler 定位 ECMASCRIPT_2018,您会得到较慢的变体。使用 TypeScript 编译器,您最终会得到两倍的对象,因为它会嵌套 Object.assign 调用。

虽然传播速度较慢,但​​每秒仍会获得大量操作。只是如果你以无聊的方式去做,你每秒会得到更多的操作。

我整理了一个 jsperf 示例来说明这一点。

https://jsperf.com/the-cost-of-spreading/1

如果您有一个可以传播的热代码路径,请考虑直接构建。否则,请不要打扰。

【讨论】:

  • 我喜欢在这个答案中提到“热代码路径”,而不是接受的答案对超大对象的扭曲。在一个数组中拥有一个非常大的对象而不是许多较小的对象,这些对象都需要某种默认值,这是非常罕见的。为此 +1!
【解决方案3】:

运行第二种方法的时间会更长(即使在现代计算机上很少),因为解释器必须遍历 commonParams 的键并将它们复制到每个对象。

编写了一个基准来找出对于小物体来说几乎为零的差异。

function runFirstApproach(){
  const employees1 = [
    {
      company: 'ABC',
      country: 'IN',
      zip: 123,
      employeeId: 123,
      employeeName: 'p'
    },
    {
      company: 'ABC',
      country: 'IN',
      zip: 123,
      employeeId: 456,
      employeeName: 'q'
    },
    {
      company: 'ABC',
      country: 'IN',
      zip: 123,
      employeeId: 789,
      employeeName: 'r'
    }
  ];
}

function runSecondApproach() {
  const commonParams = {
    company: 'ABC',
    country: 'IN',
    zip: 123
  };

  const employees2 = [
    {
      ...commonParams,
      employeeId: 123,
      employeeName: 'p'
    },
    {
      ...commonParams,
      employeeId: 456,
      employeeName: 'q'
    },
    {
      ...commonParams,
      employeeId: 789,
      employeeName: 'r'
    }
  ]
}

function runBenchmarkWithFirstApproach(){
  console.log("Avg time to run first approach -> ", getAvgRunTime(runFirstApproach, 100000))
}

function runBenchmarkWithSecondApproach(){
  console.log("Avg time to run second approach ->", getAvgRunTime(runSecondApproach, 100000))
}

function getAvgRunTime(func, rep){
  let totalTime = 0;
  let tempRep = rep;
  while(tempRep--) {
    const startTime = Date.now();
    func();
    const endTime = Date.now();
    const timeTaken = endTime-startTime;
    totalTime += timeTaken;
  }
  return totalTime/rep;
}

runBenchmarkWithFirstApproach();
runBenchmarkWithSecondApproach();

【讨论】:

    【解决方案4】:

    如果有人在想知道数组扩展操作而不是对象时偶然发现了这个问题:

    我采用了不同的方法来完成:

    const clone = [...original]
    

    var original = [];
    var clone = [];
    
    for (var i = 0; i < 10000000; i++) {
        original.push(1);
    }
    var cycle = 0;
    
    var spreadTime = [];
    var mapTime = [];
    var forTime = [];
    var reduceTime = [];
    var sliceTime = [];
    var arrayFromTime = [];
    
    while (cycle < 10) {
      var d = Date.now();
      clone = [];
      clone = [...original];
      spreadTime.push(Date.now() - d);
    
      d = Date.now();
      clone = [];
      clone = original.map((entry) => entry);
      mapTime.push(Date.now() - d);
    
      d = Date.now();
      clone = [];
      for (var i = 0; i < original.length; i++) {
          clone[i] = original[i];
      }
      forTime.push(Date.now() - d);
    
      d = Date.now();
      clone = [];
      clone = original.reduce((next, e) => {
          next.push(e);
    
          return next;
      }, []);
      reduceTime.push(Date.now() - d);
      
      d = Date.now();
      clone = [];
      clone = original.slice();
      sliceTime.push(Date.now() - d);
    
      d = Date.now();
      clone = [];
      clone = Array.from(original);
      arrayFromTime.push(Date.now() - d);
    
      cycle ++;
      document.getElementById("cycle").innerHTML = cycle;
      document.getElementById("spreadTime").innerHTML = spreadTime.reduce((a,b) => a + b, 0) / spreadTime.length;
      document.getElementById("mapTime").innerHTML = mapTime.reduce((a,b) => a + b, 0) / mapTime.length;
      document.getElementById("forTime").innerHTML = forTime.reduce((a,b) => a + b, 0) / forTime.length;
      document.getElementById("reduceTime").innerHTML = reduceTime.reduce((a,b) => a + b, 0) / reduceTime.length;
      document.getElementById("sliceTime").innerHTML = sliceTime.reduce((a,b) => a + b, 0) / sliceTime.length;
      document.getElementById("arrayFromTime").innerHTML = arrayFromTime.reduce((a,b) => a + b, 0) / arrayFromTime.length;
    }
    <View>
      <h1>cycle <span id="cycle"></span></h1>
      spread: <span id="spreadTime"></span> ms
      <br/>
      map: <span id="mapTime"></span> ms
      <br/>
      for: <span id="forTime"></span> ms
      <br/>
      reduce: <span id="reduceTime"></span> ms
      <br/>
      slice: <span id="sliceTime"></span> ms
      <br/>
      arrayFrom: <span id="arrayFromTime"></span> ms
      <br/>
    </View>

    【讨论】:

    • 任何已完成基准测试的在线站点或 github 存储库!以便这些应该用于更好地优化结果。谢谢:)
    猜你喜欢
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-29
    相关资源
    最近更新 更多