Angular 1.x vs 2 变更检测性能

3
我一直在尝试比较AngularJS 1.x和Angular 2的性能。这里有一个Plunkr,展示了Angular 1.x的一种“缺点”。如果作用域中存在太多元素,则在编辑输入字段时会注意到呈现速度变慢,因为框架将在检测到任何可能更改任何元素的事件时检查作用域中的所有元素。以下是第一个Plunkr(html)的摘录:
<body ng-app="myApp">
<div ng-controller="myCtrl">
  <input ng-model ="name"></input>
  Hello, {{name}}!
  <button ng-click="generateFields()">
    Generate 5000 elements
  </button>
  {{list}}
</div>

第一个 Plunkr(js)的摘录:

myApp.controller('myCtrl', function($scope) {
$scope.name = 'name';
$scope.list = [];

$scope.generateFields = function(){
    for(i=0; i<5000;i++){
    $scope.list.push(i);
  }
}

});

在这个Plunkr中,我用Angular 2编写了一个类似的示例。似乎完全没有延迟。那么Angular 2是如何解决这个问题的呢?框架是否知道只有输入字段发生了更改,还是由于VM优化的变更检测器而更快地执行脏检查?

第二个Plunkr的摘录:

@Component({
 selector: 'my-app',
  providers: [],
  template: `
    <div>
      <div>{{myProp}}</div>
      <input [(ngModel)]="myProp" />
      <button (click)="generateFields()">Generate</button>
      <div>{{myList}}</div>
    </div>
  `,
  directives: []
})
export class App {
  constructor() {
  }

  myProp :string = "Change me!";
  myList :any = [];

  generateFields(){
     for (var i = 1; i < 5000; i++)
     {       
          this.myList.push(i);
     }

    console.log("fields generated");
  }
}

你启用了prodMode吗?(无法在手机上打开Plunker),这样你就可以获得另外100%的提升。 - Günter Zöchbauer
因为在生产模式下变更检测仅执行一次?我没有尝试过,因为在 Angular 2 示例中我没有注意到任何滞后,不管列表中添加了多少元素。 - Fjut
没错。Angular2 CD非常高效,因为它如何使用区域和将双向绑定拆分为绑定和事件。您可以使用“ChangeDetectionStrategy.OnPush”进一步优化。 - Günter Zöchbauer
是的,我特别喜欢CD可定制的事实,这使我们在何时以及如何执行它方面拥有更多控制权。然而,我仍然不确定上述两个示例中性能差异来自何处。 - Fjut
谢谢,我现在明白了。请将您的解释添加为答案,以便我可以接受它。 - Fjut
显示剩余3条评论
2个回答

4
恰恰相反,Angular2 CD非常高效,因为它如何使用区域和双向绑定在绑定和事件中进行分离。您可以使用ChangeDetectionStrategy.OnPush进一步优化。
“因为在prodmode下变更检测只运行一次?我还没有尝试过它,因为无论列表中添加多少元素,在angular 2示例中都没有注意到任何滞后。”
我不了解Angular 1.x,因此无法告诉为什么它速度较慢。Angular2仅在订阅的事件被触发或异步调用完成时运行变更检测。Angular还不比较对象或数组的内容,只执行===检查。
“如果这是情况,即angular只执行===检查,那么在你单击第二个plunkr中的生成按钮后,它如何知道需要将更改传播到DOM?元素添加到数组中,但数组对象的引用未更改。”
我认为这是因为<div>{{myList}}</div>绑定到myList.toString(),每个变更检测周期(例如点击按钮后)它会比较结果,当它包含不同的值时结果不同。例如,如果您使用<child-comp [data]="myList">而不是{{myList}}来绑定到ChildComponent上,当ChildComponent不绑定到{{myList}}时,什么都不会发生。

我刚意识到发生了什么。请看第二个plunkr,我刚刚更新了它。现在它有一个子组件,该组件将列表作为输入参数获取。一旦我引入它,由于默认的CD策略,Angular被迫对数组进行深度检查,这意味着性能会随着生成足够的元素而显著降低。尽管如此,它仍然比angular 1快得多。底线是,在默认CD策略的情况下,输入参数会进行深度检查以进行更改,但组件“本地”变量则不会。 - Fjut
正如所提到的,我相当确定这只是由于.toString()引起的。当然,随着元素数量的增加,这需要更长的时间,但Angular本身并不进行任何深度比较。 - Günter Zöchbauer
我确信它不会。OnPush 只有在“手动”调用时才执行变更检测。我之前读过这篇文章。现在我只是搜索了一下“深入”,但只有一个无关的出现。你能告诉我你找到它的那个部分吗,这样我就不用全部阅读了。 - Günter Zöchbauer
1
我想我明白你的意思了。这并不是很清楚,我认为这只是一个不幸的表述,为了说明不可变性而已。Angular CD 不会检查数组和对象的内容和属性,除非它们在绑定表达式中被显式使用。这就是为什么到处都建议使用 observables,因为它们可以主动通知有关更改的信息,而不涉及 CD。 - Günter Zöchbauer
是的,由于属性绑定是另一种绑定表达式,因此Angular默认进行深度检查。我认为在第二个plunker中很明显,只要你生成大量元素。它清楚地表明Angular 2的脏检查机制比以前的更快,因为需要更多的元素才能开始注意到滞后。 - Fjut
显示剩余2条评论

1

“它的脏检查执行速度是否只是因为VM优化的变更检测器?”

很难说,因为Angular 2中的变更检测与Angular 1完全不同。我认为唯一相同之处是(逻辑上的)脏检查模板绑定的概念。最有可能的是Angular 2在为每个组件创建的变更检测器对象中生成的单态(VM友好/优化)代码,详情请见Angular blogthoughtram blogV.Savkin talk

请查看第二个 Plunkr,我刚刚更新了它。现在它有一个子组件,该组件将列表作为输入参数。一旦引入了它,由于默认的 CD 策略,Angular 将被迫对数组进行深度检查,这意味着性能会随着生成足够的元素而显著降低。但与 Angular 1 相比,它仍然快得多。总之,如果使用默认的 CD 策略,则会对输入参数进行深度检查,但不会对组件“本地”变量进行检查。
另外,由于属性绑定是另一种绑定表达式,因此 Angular 默认会进行深度检查。
如果模板绑定包含可迭代的内容(例如 [myList] = "myList"),则仅在开发模式下,变更检测实际上会遍历所有(例如 myList)项并进行比较,即使没有 NgFor 循环或其他创建到子组件中每个元素的模板绑定。这与在生产模式下执行的 looseIdentical() 检查(即 === 检查,因此是引用检查)非常不同。对于非常大的可迭代对象,在开发模式下可能会产生性能影响,正如您所发现的那样。
请查看https://dev59.com/tZffa4cB1Zd3GeqP5UOU#37356950以获取更深入的关于“仅devMode深度检查”的讨论。

这是否意味着,在第二个 plunkr 的示例中,在生产模式下,对于默认的 CD 策略,即使 looseIdentical() 返回 true,CD 仍将运行子组件? - Fjut
另一个问题是,当我们使用插值时,Angular如何知道它需要更新DOM,而且为什么速度如此之快?正如@Gunter所提到的那样,这是因为{{myList}}评估为myList.ToString(),因此Angular实际上比较字符串而不是循环遍历数组吗? - Fjut
1
@Fjut,默认情况下(即您未使用OnPush),在devMode和prodMode中,CD将检查每个组件中的每个模板绑定(它如何进行脏检查取决于模式)。 即使输入属性的引用没有更改(例如,myList仍然引用相同的数组),该子组件中的模板绑定仍会进行脏检查。 - Mark Rajcok
1
@Fjut,Angular在beta.16中更改了检查{{...}}数组绑定的方式,请参见https://dev59.com/pZffa4cB1Zd3GeqP4C2-。CD以前是引用检查数组,但现在我认为它像@Günter所说的那样工作...它会检查插值的字符串结果。我认为新的方式更直观。 - Mark Rajcok

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