编程题目: 方法链式调用时sleep
1. 题目
实现一个类,满足一下功能:
const brush = new Brush("red")
brush.drawLine(10).sleep(1000).drawPoint([1, 3]).drawLine(100).sleep(2000).drawLine(10)
- 期望输出
- 输出: drawLine 10 with red
- 1s 后输出: drawPoint [1, 3]
- 输入:drawLine 100 with red
- 再 2s 后输出:drawLine 10 with red
2. 思路
sleep
使得后面的链式调用都延时执行了,但其本质,返回的还是实例本身- 可以使用
Proxy
拦截该实例的所有函数执行, 然后使用setTimeout
延时执行,但都返回当前实例, 那么就实现了延时性 - 因为链式调用中,所有
sleep
都是同步执行的, 所以后面的sleep
要把前面的时间累加上去 - 为了性能考虑, 一个实例应该只创建一个
Proxy
所以用WeakMap
缓存
const bucket = new WeakMap() class Brush { constructor(color) { this.color = color this._totalTm = 0 } drawLine(length) { console.log(new Date(), `drawLine ${length} with ${this.color}`) return this } drawPoint(coor) { console.log(new Date(), `drawPoint [${coor[0]}, ${coor[1]}]`) return this } sleep(tm) { const _this = this.__raw ?? this; let cached = bucket.get(_this) _this._totalTm += tm if (!cached) { cached = new Proxy(this, { get(target, key) { if (key === '__raw') { return _this } const rawValue = Reflect.get(...arguments) if (typeof target[key] === 'function' && key !== 'sleep') { // 除了 sleep 都拦截 const rawFn = rawValue return new Proxy(rawFn, { apply(...args) { setTimeout(() => { Reflect.apply(...args) }, _this._totalTm) return cached } }) } return rawValue } }) bucket.set(this, cached) } return cached } } const brush = new Brush("red") console.log(new Date(), 'run') brush.drawLine(10) .sleep(1000).drawPoint([1, 3]).drawLine(100) .sleep(2000).drawLine(10) .sleep(1000).drawLine(8)
2023-03-08T08:00:00.468Z run 2023-03-08T08:00:00.470Z drawLine 10 with red 2023-03-08T08:00:01.472Z drawPoint [1, 3] 2023-03-08T08:00:01.473Z drawLine 100 with red 2023-03-08T08:00:03.471Z drawLine 10 with red 2023-03-08T08:00:04.471Z drawLine 8 with red
3. 再加一个 destroy
, 可以清理掉所有延时执行的方法
思路:
- 收集 timerId
- 调用
destroy
清理它
const bucket = new WeakMap() class Brush { constructor(color) { this.color = color this._totalTm = 0 this._timerIds = [] } drawLine(length) { console.log(new Date(), `drawLine ${length} with ${this.color}`) return this } drawPoint(coor) { console.log(new Date(), `drawPoint [${coor[0]}, ${coor[1]}]`) return this } destroy() { this._timerIds.forEach(clearTimeout) } sleep(tm) { const _this = this.__raw ?? this; let cached = bucket.get(_this) _this._totalTm += tm if (!cached) { cached = new Proxy(this, { get(target, key) { if (key === '__raw') { return _this } const rawValue = Reflect.get(...arguments) if (typeof target[key] === 'function' && key !== 'sleep') { // 除了 sleep 都拦截 const rawFn = rawValue return new Proxy(rawFn, { apply(...args) { let id = setTimeout(() => { Reflect.apply(...args) _this._timerIds.splice(_this._timerIds.indexOf(id), 1) }, _this._totalTm) _this._timerIds.push(id) return cached } }) } return rawValue } }) bucket.set(this, cached) } return cached } } const brush = new Brush("red") console.log(new Date(), 'run') brush.drawLine(10) .sleep(1000).drawPoint([1, 3]).drawLine(100) .sleep(2000).drawLine(10) .sleep(1000).drawLine(8) setTimeout(() => { brush.destroy(); }, 3500)
2023-03-08T08:00:04.556Z run 2023-03-08T08:00:04.557Z drawLine 10 with red 2023-03-08T08:00:05.560Z drawPoint [1, 3] 2023-03-08T08:00:05.561Z drawLine 100 with red 2023-03-08T08:00:07.559Z drawLine 10 with red
4. 抽象化
- 这个逻辑,其实与
Brush
类本身无关, 所以可以抽离成一个类,然后用Brush
继承它
const bucket = new WeakMap() const RAW = Symbol() class Sleeper { constructor() { this._timerIds = [] this._totalTm = 0 } destroy() { this._timerIds.forEach(clearTimeout) } sleep(tm) { const _this = this[RAW] ?? this; let cached = bucket.get(_this) _this._totalTm += tm if (!cached) { cached = new Proxy(this, { get(target, key) { if (key === RAW) { return _this } const rawValue = Reflect.get(...arguments) if (typeof target[key] === 'function' && key !== 'sleep') { // 除了 sleep 都拦截 const rawFn = rawValue return new Proxy(rawFn, { apply(...args) { let id = setTimeout(() => { Reflect.apply(...args) _this._timerIds.splice(_this._timerIds.indexOf(id), 1) }, _this._totalTm) _this._timerIds.push(id) return cached } }) } return rawValue } }) bucket.set(this, cached) } return cached } } class Brush extends Sleeper { constructor(color) { super() this.color = color } drawLine(length) { console.log(new Date(), `drawLine ${length} with ${this.color}`) return this } drawPoint(coor) { console.log(new Date(), `drawPoint [${coor[0]}, ${coor[1]}]`) return this } } const brush = new Brush("red") console.log(new Date(), 'run') brush.drawLine(10) .sleep(1000).drawPoint([1, 3]).drawLine(100) .sleep(2000).drawLine(10) .sleep(1000).drawLine(8) setTimeout(() => { brush.destroy(); }, 3500)
2023-03-08T08:00:08.147Z run 2023-03-08T08:00:08.148Z drawLine 10 with red 2023-03-08T08:00:09.151Z drawPoint [1, 3] 2023-03-08T08:00:09.152Z drawLine 100 with red 2023-03-08T08:00:11.150Z drawLine 10 with red