考题 最近遇到一道面试题
new Human(‘小明’).sleep(3).go(‘home’).eat(‘apple’).sleep(2).eat(‘apple 2’) 输出 // I am 小明 // waiting: sleep for 3s // go home // eat an apple // waiting: sleep for 2s // eat an apple 2
解析 这道题的难点在于 sleep
方法。
常见的 sleep
方式实现:
live demo
1 2 3 4 5 6 7 8 9 10 11 function sleep (time ) { return new Promise (resolve => { setTimeout(resolve, time); }); } (async function ( ) { console .log("start" ); await sleep(3000 ); console .log("end" ); })();
题目中的调用方式是链式调用,如果要做到 sleep,则需要把所有调用的方法进行收集,等待 sleep 之后再调用堆栈中的下一项。所以,我们把在方法内构造一个匿名函数,然后将函数 push 进堆栈,等待之后调用。
实现 live demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 class Human { name: string ; tasks: any []; constructor (name: string ) { this .name = name; this .tasks = []; this .say("I am " + this .name); this .start(); } start() { const that = this ; setTimeout(() => { that.runNext(); }, 0 ); } say(what) { console .log(what); } addTask(fn) { this .tasks.push(fn); } runNext() { const task = this .tasks.shift(); task && task(); } sleep(time) { const that = this ; const anon = function ( ) { console .log("waiting: sleep for " + time); setTimeout(() => { that.runNext(); }, time * 1000 ); }; this .addTask(anon); return this ; } go(where) { const that = this ; const anon = function ( ) { that.say("go " + where); that.runNext(); }; this .addTask(anon); return this ; } eat(what) { const that = this ; const anon = function ( ) { that.say("Eat an " + what); that.runNext(); }; this .addTask(anon); return this ; } } var aa = new Human("小明" ) .sleep(3 ) .go("home" ) .eat("apple" ) .sleep(2 ) .eat("apple too" );
上边的实现虽然满足了要求,但是代码却很多重复,我们 decorator
进行重构下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 function taskDecorator (autoNext ) { return function (target, name, descriptor ) { if (descriptor) { const oldValue = descriptor.value; descriptor.value = function (...args ) { const that = this ; const newValue = function ( ) { const ret = oldValue.apply( that, args.concat(that.runNext.bind(that)) ); if (autoNext) { that.runNext(); } return ret; }; this .addTask(newValue); return this ; }; return descriptor; } }; } class Human { name: string ; tasks: any []; constructor (name: string ) { this .name = name; this .tasks = []; this .say("I am " + this .name); this .start(); } start() { const that = this ; setTimeout(() => { that.runNext(); }, 0 ); } say(what) { console .log(what); } addTask(fn) { this .tasks.push(fn); } runNext() { const task = this .tasks.shift(); task && task(); } @taskDecorator (false ) sleep(time, next?) { console .log("waiting: sleep for " + time); setTimeout(() => { next && next(); }, time * 1000 ); return this ; } @taskDecorator (true ) go(where) { this .say("go: " + where); return this ; } @taskDecorator (true ) eat(what) { this .say("Eat an " + what); return this ; } } var aa = new Human("jack" ) .sleep(3 ) .go("home" ) .eat("apple" ) .sleep(2 ) .eat("apple too" );
我们提取了 @taskDecorator
装饰器,用于装饰需要添加到任务队列的方法。装饰器支持一个参数,告诉装饰器是否自动调用 runNext
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 descriptor.value = function (...args ) { const that = this ; const newValue = function ( ) { const ret = oldValue.apply(that, args.concat(that.runNext.bind(that))); if (autoNext) { that.runNext(); } return ret; }; this .addTask(newValue); return this ; };
对不自动调用的方法,提供了参数注入的形式,注入到方法的最后一个参数上:
1 const ret = oldValue.apply(that, args.concat(that.runNext.bind(that)));