在这段故事中,我们将通过一个简单的例子来演示这三个操作符是如何工作的。我跳过了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()} 完成`);
}
);
}
- timer(0,330) 立刻发出第一个值 0 ,随后每隔 330ms 发出一个值。它会持续发出值 1000ms,然后完成。这一点可以从 takeUntil(timer(1000)) 看出来。
- 当第一个值 0 发出时,第一个审核间隔开始,持续 100ms。
- 注意,在第一个审核间隔完成后的 100ms,auditTime 操作符会将源发出的最新值 0 发送给订阅者。
- 在源发出 0 后 330ms,源发出第二个值 1,现在第二个审核间隔开始,持续 100ms。
- 在 100ms 后,第二个审核间隔完成,auditTime 操作符会将最新的值 1 发送给订阅者。
- 在源发出 1 后 330ms,源发出第三个值 2,现在第三个审核间隔开始,持续 100ms。
- 在 100ms 后,第三个审核间隔完成,auditTime 操作符会将最新的值 2 发送给订阅者。
- 在源发出 2 后 330ms,源发出第四个值 3,现在第四个审核间隔开始,持续 100ms。
- 从 timer 操作符发出 0 后已经过去了 990ms。10ms 后,源将完成。但是第四个审核间隔将在 100ms 后完成。这意味着源将在第四个审核间隔完成前完成。因此,源发出的最新和最后一个值 3 不会被发送给订阅者,因为源在第四个审核间隔完成前已经完成。
- 因为源已经完成,并不再发出任何值,所以不会再有进一步的审核间隔被安排。
II. 采样时间
sampleTime 操作符与 auditTime 非常相似,但有一点不同。sampleTime 操作符不会像 auditTime 那样等待源发出值再安排采样间隔。操作符会持续安排采样间隔,不管源是否发出新值。
- sampleTime 操作符设置一个新的采样时间为 N 毫秒。
- 在此间隔内,可观察对象可能会或可能不会发出值,但是操作符会确保在此间隔内不会向订阅者发送任何值。
- 间隔结束后,操作符会将源在间隔结束前发出的最后一个值发送给订阅者。
- 如果源在采样间隔结束前已经完成,则 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()}`);
}
);
}
- sampleTime 操作符将调度第一个采样间隔。
- timer(0,330) 立即发出第一个值 0,随后每 330毫秒 发出一个新值。它持续 1000毫秒 后停止发出值,然后完成。这一点可以从 takeUntil(timer(1000)) 中看出。
- 注意,当第一个 100毫秒 的采样间隔完成后,sampleTime 操作符会将源发出的最新值,即 0,发送给订阅者。
- 第一个采样间隔完成后,sampleTime 操作符将安排开始第二个采样间隔,即 100毫秒 的间隔。请注意,此时源尚未发出任何新值。因此,无论源是否发出值,新的间隔都会被安排。
- 在源从发出 0 开始的 330毫秒 后,源发出第二个值:1。
- 在 100毫秒 后,第二个采样间隔结束,sampleTime 操作符会发送最新的值 1 给订阅者。
- 由于第二个采样间隔已经结束,sampleTime 操作符将安排开始第三个采样间隔,即 100毫秒 的间隔。
- 在源从发出 1 开始的 330毫秒 后,源发出第三个值:2。
- 在 100毫秒 后,第三个采样间隔结束,sampleTime 操作符会发送最新的值 2 给订阅者。
- 由于第三个采样间隔结束,sampleTime 操作符将安排开始第四个采样间隔,即 100毫秒 的间隔。
- 在源从发出 2 开始的 330毫秒 后,源发出第四个值:3。
- 注意,从 timer 操作符发出 0 以来已经过去了 990毫秒。10毫秒后,我们预计源将终止。但是第四个 100毫秒 的采样间隔将在100毫秒后结束。这意味着源将在第四个采样间隔结束之前就终止。源发出的最后一个值 3 将不会发送给订阅者,因为源在第四个采样间隔结束之前就已经终止。
III. 去抖时间
操作员的工作方式如下。
- 源可观察对象发出一个值。
- 为这个新值安排一个新的 debounce 时间间隔(N ms)。
- debounceTime 操作符会在 debounce 时间间隔 结束,且在此期间没有其他值被源发出的情况下,才将值传递给订阅者。
- 如果在 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()}`);
}
);
}
-
timer(0,500) 立即发出第一个值 0,随后每隔 500毫秒 发出新的值。它将持续发出值直到 10000毫秒 后完成,这一点可以看出 takeUntil(timer(10000))。
-
当第一个值:0 发出时,会安排一个 2000毫秒 的去抖时间。如果源在 2000毫秒 内没有发出新的值,debounceTime 操作符会将值 0 发送给订阅者。
-
我们知道 timer 操作符每隔 500毫秒 发出一个新值,这意谓著每次新值发出时,都会安排一个新的 2000毫秒 的去抖动时间。
- 因此,没有一个去抖时间会在该时间段内没有新值发出的情况下完成。这意味着源发出的所有值都不会被传递给订阅者。
如下截图中可以看到上述观察。
现在让我们修改示例,让定时器每隔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()}`);
}
);
}
- timer(0,3000) 立即发出第一个值 0,随后每 3000ms 发出新的值。它持续 10000ms 后完成,这一点可以通过 takeUntil(timer(10000)) 看出。
- 当第一个值:0 被发出时,一个 2000ms 的延迟间隔被安排。如果在 2000ms 内源没有发出新的值,那么 debounceTime 操作符会将值 0 发送给订阅者。
- 我们知道,timer 操作符每 3000ms 发出一个新的值。这意味着,当源发出下一个新值时,2000ms 的延迟间隔已经结束。
- 因此,每个延迟间隔都会在未收到新的源值前完成,这意味着源发出的所有值,除了最后一个值,都会被发送给订阅者。
- 最后一个值不会被发送给订阅者,因为源在延迟间隔结束之前就已经完成。
如果我稍微修改一下同一个示例,将 takeUntil(timer(10000)) 更新为 takeUntil(timer(12000)),源代码将在 2 秒后完成。如下面的截图可以看到,现在订阅者可以接收到最后一个值了 :3.
下面是这三个运算符的一个工作示例。
栈学 🎓谢谢您看到最后了,在您离开之前:
- 请考虑为作者点赞和关注他!👏
- 关注我们 X | 领英 | YouTube | Discord | 通讯简报 | Podcast
- 在Differ上免费创建一个AI驱动的博客。
- 更多内容请看 Stackademic.com