为了账号安全,请及时绑定邮箱和手机立即绑定

Angular:我如何理解这三个基于时间的RxJS操作符:auditTime、sampleTime和debounceTime

在这段故事中,我们将通过一个简单的例子来演示这三个操作符是如何工作的。我跳过了throttleTime操作符,因为它稍微复杂一点。我将在另一个故事里讲讲throttleTime操作符!

auditTime、sampleTime 和 debounceTime 都是 速率限制 操作符,如果你希望在特定时间内忽略源观察者,它们会很有用。它们操作方式的不同使得它们适用于不同的场景。例如,debounceTime 在限制自动完成功能的输入框中的用户输入速率时非常有用,而 auditTime 可用于限制滚动时发出的滚动事件的数量。我们不仅限于滚动事件,还包括其他 DOM 事件,如调整尺寸、鼠标移动和按键事件。

I. 审核时间

我们来看看这个操作符是怎么工作的:

当源可观察对象发出一个值时,auditTime(审计时间)操作符会开始一个N毫秒的延迟。

2. 在这段间隔期间,数据源可能会发出值,也可能不会发出值,但操作符确保在这段间隔内不会有任何值发送给监听者。

3. 间隔结束后,操作者把源发出的最新值传给订阅者。

4. 每次间隔结束后,过程将从1)重新开始,直到源结束。

5. 如果源在间隔完成前就完成了,那么操作符不会将源在它完成之前最后发出的值传递给订阅者。

故事中的所有例子都使用下面的getDate()方法来获取事件的getDate()时间戳

// 获取当前时间,返回格式为 '小时:分钟:秒:毫秒'
    getDate() {  
        let date = new Date();  
        return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}:${date.getMilliseconds()}`;  
     }

我们来看一个例子理解这个运算符。


    implementAuditTime() {

    timer(0, 330).pipe(
    tap((num: number) => {
    console.log(`源在 ${this.getDate()} 发出 ${num}`); 
    }), 
    auditTime(100), 
    tap(() => {
    console.log(`100毫秒的审计间隔已过在 ${this.getDate()}`); 
    }), 
    takeUntil(timer(1000))
    )
    .subscribe(
    (result) => {
    console.log(`订阅者在 ${this.getDate()} 收到 ${result}`); 
    }, 
    (err) => {}, 
    () => {
    console.log(`源在 ${this.getDate()} 完成`); 
    }
    );
    }

  1. timer(0,330) 立刻发出第一个值 0 ,随后每隔 330ms 发出一个值。它会持续发出值 1000ms,然后完成。这一点可以从 takeUntil(timer(1000)) 看出来。
  2. 当第一个值 0 发出时,第一个审核间隔开始,持续 100ms
  3. 注意,在第一个审核间隔完成后的 100msauditTime 操作符会将源发出的最新值 0 发送给订阅者。
  4. 在源发出 0330ms,源发出第二个值 1,现在第二个审核间隔开始,持续 100ms
  5. 100ms 后,第二个审核间隔完成,auditTime 操作符会将最新的值 1 发送给订阅者。
  6. 在源发出 1330ms,源发出第三个值 2,现在第三个审核间隔开始,持续 100ms
  7. 100ms 后,第三个审核间隔完成,auditTime 操作符会将最新的值 2 发送给订阅者。
  8. 在源发出 2330ms,源发出第四个值 3,现在第四个审核间隔开始,持续 100ms
  9. timer 操作符发出 0 后已经过去了 990ms。10ms 后,源将完成。但是第四个审核间隔将在 100ms 后完成。这意味着源将在第四个审核间隔完成前完成。因此,源发出的最新和最后一个值 3 不会被发送给订阅者,因为源在第四个审核间隔完成前已经完成。
  10. 因为源已经完成,并不再发出任何值,所以不会再有进一步的审核间隔被安排。

II. 采样时间

sampleTime 操作符与 auditTime 非常相似,但有一点不同。sampleTime 操作符不会像 auditTime 那样等待源发出值再安排采样间隔。操作符会持续安排采样间隔,不管源是否发出新值。

  1. sampleTime 操作符设置一个新的采样时间为 N 毫秒。
  2. 在此间隔内,可观察对象可能会或可能不会发出值,但是操作符会确保在此间隔内不会向订阅者发送任何值。
  3. 间隔结束后,操作符会将源在间隔结束前发出的最后一个值发送给订阅者。
  4. 如果源在采样间隔结束前已经完成,则 sampleTime 操作符不会发送源在完成前所发出的最新值给订阅者。

我们来看一个例子来理解一下。

    implementSampleTime() {  
     timer(0, 330)  
          .pipe(  
            tap((num: number) => {  
              console.log(`源发出了 ${num},时间是 ${this.getDate()}`);  
            }),  
            sampleTime(100),  
            tap(() => {  
              console.log(`在${this.getDate()},100毫秒的采样时间结束了`);  
            }),  
            takeUntil(timer(1000))  
          )  
          .subscribe(  
            (result) => {  
              console.log(`订阅者收到了 ${result},时间是 ${this.getDate()}`);  
            },  
            (err) => {},  
            () => {  
              console.log(`源结束了,时间是 ${this.getDate()}`);  
            }  
          );  
    }

  1. sampleTime 操作符将调度第一个采样间隔。
  2. timer(0,330) 立即发出第一个值 0,随后每 330毫秒 发出一个新值。它持续 1000毫秒 后停止发出值,然后完成。这一点可以从 takeUntil(timer(1000)) 中看出。
  3. 注意,当第一个 100毫秒 的采样间隔完成后,sampleTime 操作符会将源发出的最新值,即 0,发送给订阅者。
  4. 第一个采样间隔完成后,sampleTime 操作符将安排开始第二个采样间隔,即 100毫秒 的间隔。请注意,此时源尚未发出任何新值。因此,无论源是否发出值,新的间隔都会被安排。
  5. 在源从发出 0 开始的 330毫秒 后,源发出第二个值:1。
  6. 100毫秒 后,第二个采样间隔结束,sampleTime 操作符会发送最新的值 1 给订阅者。
  7. 由于第二个采样间隔已经结束,sampleTime 操作符将安排开始第三个采样间隔,即 100毫秒 的间隔。
  8. 在源从发出 1 开始的 330毫秒 后,源发出第三个值:2。
  9. 100毫秒 后,第三个采样间隔结束,sampleTime 操作符会发送最新的值 2 给订阅者。
  10. 由于第三个采样间隔结束,sampleTime 操作符将安排开始第四个采样间隔,即 100毫秒 的间隔。
  11. 在源从发出 2 开始的 330毫秒 后,源发出第四个值:3。
  12. 注意,从 timer 操作符发出 0 以来已经过去了 990毫秒。10毫秒后,我们预计源将终止。但是第四个 100毫秒 的采样间隔将在100毫秒后结束。这意味着源将在第四个采样间隔结束之前就终止。源发出的最后一个值 3 将不会发送给订阅者,因为源在第四个采样间隔结束之前就已经终止。

III. 去抖时间

操作员的工作方式如下。

  1. 源可观察对象发出一个值。
  2. 为这个新值安排一个新的 debounce 时间间隔(N ms)。
  3. debounceTime 操作符会在 debounce 时间间隔 结束,且在此期间没有其他值被源发出的情况下,才将值传递给订阅者。
  4. 如果在 debounce 时间间隔结束之前源发出了新的值,则之前的值将被丢弃,不会传递给订阅者。流程将回到步骤 2) 重新开始。

让我们来看一个例子来理解吧:

    implementDebounceTime() {  

    timer(0, 500).pipe(  
    tap((num: number) => console.log(`源发出了 ${num},时间:` ${this.getDate()}`)),  
    debounceTime(2000),  
    tap(() => console.log(`延时等待已结束,时间:` ${this.getDate()}`)),  
    takeUntil(timer(10000))  
    )  
    .subscribe(  
    (result) => {  
    console.log(`订阅者收到 ${result},时间:` ${this.getDate()}`); //5,11,17…  
    },  
    (err) => {},  
    () => {  
    console.log(`源已结束,时间:` ${this.getDate()}`);  
    }  
    );  
    }
  1. timer(0,500) 立即发出第一个值 0,随后每隔 500毫秒 发出新的值。它将持续发出值直到 10000毫秒 后完成,这一点可以看出 takeUntil(timer(10000))

  2. 当第一个值:0 发出时,会安排一个 2000毫秒 的去抖时间。如果源在 2000毫秒 内没有发出新的值,debounceTime 操作符会将值 0 发送给订阅者。

  3. 我们知道 timer 操作符每隔 500毫秒 发出一个新值,这意谓著每次新值发出时,都会安排一个新的 2000毫秒 的去抖动时间。

  4. 因此,没有一个去抖时间会在该时间段内没有新值发出的情况下完成。这意味着源发出的所有值都不会被传递给订阅者。

如下截图中可以看到上述观察。

现在让我们修改示例,让定时器每隔3000毫秒发出一个新的值。

implementDebounceTime() {  

timer(0, 3000).pipe(  
tap((num: number) => console.log(`源发出了 ${num},时间为 ${this.getDate()}`)),  
debounceTime(2000),  
tap(() => console.log(`计时器到了,时间为 ${this.getDate()}`)),  
takeUntil(timer(10000))  
)  
.subscribe(  
(result) => {  
console.log(`订阅者收到 ${result},时间为 ${this.getDate()}`); //5,11,17…  
},  
(err) => {},  
() => {  
console.log(`源已经完成,时间为 ${this.getDate()}`);  
}  
);  
}

  1. timer(0,3000) 立即发出第一个值 0,随后每 3000ms 发出新的值。它持续 10000ms 后完成,这一点可以通过 takeUntil(timer(10000)) 看出。
  2. 当第一个值:0 被发出时,一个 2000ms 的延迟间隔被安排。如果在 2000ms 内源没有发出新的值,那么 debounceTime 操作符会将值 0 发送给订阅者。
  3. 我们知道,timer 操作符每 3000ms 发出一个新的值。这意味着,当源发出下一个新值时,2000ms 的延迟间隔已经结束。
  4. 因此,每个延迟间隔都会在未收到新的源值前完成,这意味着源发出的所有值,除了最后一个值,都会被发送给订阅者。
  5. 最后一个值不会被发送给订阅者,因为源在延迟间隔结束之前就已经完成。

如果我稍微修改一下同一个示例,将 takeUntil(timer(10000)) 更新为 takeUntil(timer(12000)),源代码将在 2 秒后完成。如下面的截图可以看到,现在订阅者可以接收到最后一个值了 :3.

下面是这三个运算符的一个工作示例。

栈学 🎓

谢谢您看到最后了,在您离开之前:

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消