The case for ZoneDelegate

拦截 zone 事件是棘手的,因为 zone 是在运行时生成的,并且标准方法(如子类化和猴子补丁)只有在设计时已知父 zone时才起作用。 为了更好地演示这个问题,我们假设想在run()方法的前后进行拦截,这样我们可以测量执行时间和在控制台记录 zone。

这是一个破例来说明这个问题。

  1. // 在设计时,不可能知道哪个 zone 将是父 zone。 因此,父 zone 必须传递到构造函数中。
  2. class TimingZone extends Zone {
  3. constructor(parent) { super(parent, 'timingZone');}
  4. // We would like to intercept the run, and so we overwrite it.
  5. run() {
  6. // Capture the start time
  7. var start = performance.now();
  8. // It may appear that calling super.run() is the right thing
  9. // to do at this point, but the super.run(), internally must
  10. // invoke parent.run. So super.run is just a red herring,
  11. // and we should rather understand what are the consequences
  12. // of parent.run(). See next example.
  13. super.run(...arguments);
  14. // Capture the end time
  15. var end = performance.now();
  16. // Print the duration, and the current zone.
  17. console.log(this.name, 'Duration:', end - start);
  18. }
  19. }
  20. class LogZone extends Zone {
  21. constructor(parent) { super(parent, 'logZone');}
  22. run() {
  23. // log the zone name and 'enter'
  24. console.log(this.name, 'enter');
  25. // The issue with calling parent.run, is that it will cause the
  26. // current zone to be changed to the parent zone.
  27. // What we need is a way of calling the parent run hooks, without
  28. // changing the current zone.
  29. this.parent.run.apply(this, arguments);
  30. // log the zone name and 'leave'
  31. console.log(this.name, 'leave');
  32. }
  33. }

让我们使用上述 zone 创建一个简单的例子。

  1. // Compose several zones.
  2. let rootZone = Zone.current;
  3. let timingZone = new TimingZone(rootZone);
  4. let logZone = new LogZone(timingZone);
  5. logZone.run(() => {
  6. console.log(Zone.current.name, 'Hello World!');
  7. });

Here is what one would expect from the above example. Specifically the expectation is that the current zone inside the run block will be that of the logZone.

这里是上面例子的输出

  1. logZone enter
  2. logZone Hello World;
  3. logZone Duration: 0.123
  4. logZone leave

为什么标准工具不起作用

拦截调用(super或父级)的标准方式不起作用的原因是父 zone 在设计时是未知的。 如果父 zone 在设计时间是已知的,那么我们可以这样写:

  1. class TimingZone extends RootZone {
  2. run() {
  3. super.run(...arguments);
  4. }
  5. }
  6. class LogZone extends TimingZone {
  7. run() {
  8. super.run(...arguments);
  9. }
  10. }

如果我们可以在设计时决定 zone 的层次结构,super.run()的调用将按预期工作。 然而,因为父进程在运行时之前是未知的,所以我们需要将更改 zone 的行为与调用父钩子的行为分开。 为了解决这个问题,钩子被指定为fork() 的一部分,钩子接收一个父ZoneDelagate(只处理钩子)而不是一个父 zone (这将导致一个 zone 改变)。

Zone ZoneDelegate 描述
run invoke 当执行zone run()块时,调用invoke() ZoneDelegate钩子。 这允许在不改变当前 zone 的情况下委派钩子。
wrap intercept 当zone wrap() 块被执行时,intercept() ZoneDelegate钩子被调用。 这允许委托钩子而不会重新包装回调。