分享一道面试题:
实现一个LazyMan,可以按照以下方式调用:
LazyMan("Hank")输出:
Hi! This is Hank!
LazyMan("Hank").sleep(10).eat("dinner")输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan("Hank").eat("dinner").eat("supper")输出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan("Hank").sleepFirst(5).eat("supper")输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
以此类推。
涉及到的技术点:
链式编程
JS流程控制
JS事件循环机制Promise
。。。
方法1:
问题的关键是如何实现任务的顺序执行。
参考node.js,在Express有一个类似的东西叫中间件,这个中间件和我们这里的吃饭、睡觉等任务很类似。
每一个中间件执行完成后会调用next()函数,这个函数用来调用下一个中间件。
对于这个问题,我们也可以利用相似的思路来解决。
首先创建一个任务队列,然后利用next()函数来控制任务的顺序执行:
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 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 | function LazyMan(name) { // 创建一个任务队列 let taskList = []; // 创建lazyman对象 let _lazyMan = { // 执行下一步 的方法 next() { // 抽取任务队列中的第一个任务 let task = taskList.shift(); // 如果存在该任务,就调用该任务 task && task() }, // sayHi(打招呼) 方法 sayHi(name) { // 在任务队列 最后面 追加任务 taskList.push(() => { // 打招呼 console.log(`Hi! This is ${name}!`); // 该任务完成之后,调用下一步的方法 this.next() }) // return this 为了实现链式编程 return this }, // sleep(睡觉) 方法 sleep(time) { // 在任务队列 最后面 追加任务 taskList.push(() => { // 开启定时器 setTimeout(() => { // 输入 多少秒之后醒来 console.log(`Wake up after ${time}`); // 该任务完成之后,调用下一步的方法 this.next() }, time * 1000); }) // return this 为了实现链式编程 return this }, eat(food) { taskList.push(() => { console.log(`Eat ${food}~`); this.next() }) return this }, // sleepFirst(先睡) 方法 sleepFirst(time) { // 在任务队列 最前面 添加任务 taskList.unshift(() => { // 开启定时器 setTimeout(() => { console.log(`Wake up after ${time}`); // 该任务完成之后,调用下一步的方法 this.next() }, time * 1000); }) // return this 为了实现链式编程 return this } } // 手动调用 sayHi方法 _lazyMan.sayHi(name) // 使用定时器,让任务队列在同步线程完成之后再执行 setTimeout(() => { _lazyMan.next() }, 0); // 暴露 lazyman对象 return _lazyMan}// LazyMan("Hank")// LazyMan("Hank").sleep(5).eat("dinner")// LazyMan("Hank").eat("dinner").eat("supper")LazyMan("Hank").sleepFirst(5).eat("supper") |
方法2:
1. 看题目输出示例,可以确定这是拟人化的输出,也就是说:应该编写一个类来定义一类人,叫做LazyMan。可以输出名字、吃饭、睡觉等行为。
2. 从输出的句子可以看出,sleepFrist的优先级是最高的,其他行为的优先级一致。
3. 从三个例子来看,都得先调用LazyMan来初始化一个人,才能继续后续行为,所以LazyMan是一个接口。
4. 句子是按调用方法的次序进行顺序执行的,是一个队列。
[JavaScript]
纯文本查看
复制代码
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | // 采用模块模式来编写代码(function (window, undefined) { // 创建一个任务队列 var taskList = []; // { // 'msg': 'LazyMan', // 消息名 // 'args': 'Hank' // 参数列表 // } // 订阅 function subscribe() { var args = Array.prototype.slice.call(arguments); if (args.length < 1) { throw new Error("subscribe 参数不能为空!"); } // 创建任务 var task = { msg: args[0], // 消息名 args: args.slice(1) // 参数列表 } // 除非是 sleepFirst 向前添加,否则向后追加 if (task.msg == "sleepFirst") { taskList.unshift(task); } else { taskList.push(task); } } // 发布 function publish() { if (taskList.length > 0) { // 调用 run(执行)方法 run(taskList.shift()); } } // 类 function LazyMan() {}; LazyMan.prototype.eat = function (str) { subscribe("eat", str); return this; }; LazyMan.prototype.sleep = function (num) { subscribe("sleep", num); return this; }; LazyMan.prototype.sleepFirst = function (num) { subscribe("sleepFirst", num); return this; }; // 输出文字 function lazyManLog(str) { console.log(str); } // 具体方法 // 打招呼 function lazyMan(str) { lazyManLog("Hi!This is " + str + "!"); publish(); } // 吃 function eat(str) { lazyManLog("Eat " + str + "~"); publish(); } // 睡 function sleep(num) { setTimeout(function () { lazyManLog("Wake up after " + num); publish(); }, num * 1000); } // 先睡 function sleepFirst(num) { setTimeout(function () { lazyManLog("Wake up after " + num); publish(); }, num * 1000); } // run(执行)方法: function run(option) { var msg = option.msg, args = option.args; switch (msg) { case "lazyMan": lazyMan.apply(null, args); break; case "eat": eat.apply(null, args); break; case "sleep": sleep.apply(null, args); break; case "sleepFirst": sleepFirst.apply(null, args); break; default: ; } } // 暴露接口 window.LazyMan = function (str) { subscribe("lazyMan", str); setTimeout(function () { publish(); }, 0); return new LazyMan(); };})(window);// LazyMan("Hank")// LazyMan("Hank").sleep(5).eat("dinner")// LazyMan("Hank").eat("dinner").eat("supper")LazyMan("Hank").sleepFirst(5).eat("supper") |
Promise版:
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 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 | function _LazyMan(name) { this.promiseGetters = []; var makePromise = function () { var promiseObj = new Promise(function(resolve, reject){ console.log("Hi! This is " + name + "!"); resolve(); }) return promiseObj; } this.promiseGetters.push(makePromise); // 在各个Promise的then函数中,将任务序列穿起来 var self = this; var sequence = Promise.resolve(); // Promise.resolve 等价于 // var sequence = new Promise(function (resolve, reject) { // resolve(); // }) setTimeout(function(){ for (var i = 0; i < self.promiseGetters.length; i++) { var nowPromiseGetter = self.promiseGetters[i]; var thenFunc = (function (nowPromiseGetter) { return function () { return nowPromiseGetter() } })(nowPromiseGetter); sequence = sequence.then(thenFunc); }; }, 0); // 在下一个事件循环启动任务}_LazyMan.prototype.eat = function(name) { var makePromise = function () { var promiseObj = new Promise(function(resolve, reject){ console.log("Eat " + name + "~"); resolve(); }) return promiseObj; } this.promiseGetters.push(makePromise); return this; // 实现链式调用}_LazyMan.prototype.sleep = function(time) { var makePromise = function () { var promiseObj = new Promise(function(resolve, reject){ setTimeout(function(){ console.log("Wake up after " + time + "s!"); resolve(); }, time * 1000); }) return promiseObj; } this.promiseGetters.push(makePromise); return this;}/* 封装 */function LazyMan(name){ return new _LazyMan(name);}LazyMan("Hank").sleep(1).eat("dinner") |