分享一道面試題:
實現一個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 suppernode
以此類推。面試
涉及到的技術點:
鏈式編程
JS流程控制
JS事件循環機制Promise
。。。編程
方法1:
問題的關鍵是如何實現任務的順序執行。
參考node.js,在Express有一個相似的東西叫中間件,這個中間件和咱們這裏的吃飯、睡覺等任務很相似。
每個中間件執行完成後會調用next()函數,這個函數用來調用下一個中間件。
對於這個問題,咱們也能夠利用類似的思路來解決。
首先建立一個任務隊列,而後利用next()函數來控制任務的順序執行:
[JavaScript] 純文本查看 複製代碼
?promise
function LazyMan(name) {app
// 建立一個任務隊列 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")this
方法2:prototype
2. 從輸出的句子能夠看出,sleepFrist的優先級是最高的,其餘行爲的優先級一致。
3. 從三個例子來看,都得先調用LazyMan來初始化一我的,才能繼續後續行爲,因此LazyMan是一個接口。
4. 句子是按調用方法的次序進行順序執行的,是一個隊列。線程
[JavaScript] 純文本查看 複製代碼
?code
// 採用模塊模式來編寫代碼
(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] 純文本查看 複製代碼
?
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")更多技術資訊可關注:gzitcast