一、題目介紹
以下是我copy自網上的面試題原文:
實現一個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
以此類推。
二、題目考察的點
先聲明:我不是微信員工,考察的點是我推測的,可能不是,哈哈!
1.方法鏈式調用
2.類的使用和面向對象編程的思路
3.設計模式的應用
4.代碼的解耦
5.最少知識原則,也即 迪米特法則(Law of Demeter)
6.代碼的書寫結構和命名
三、題目思路解析
1.看題目輸出示例,可以確定這是擬人化的輸出,也就是說:應該編寫一個類來定義一類人,叫做LazyMan。可以輸出名字、吃飯、睡覺等行為。
2.從輸出的句子可以看出,sleepFrist的優先級是最高的,其他行為的優先級一致。
3.從三個例子來看,都得先調用LazyMan來初始化一個人,才能繼續后續行為,所以LazyMan是一個接口。
4.句子是按調用方法的次序進行順序執行的,是一個隊列。
四、采用觀察者模式實現代碼
4.1 采用模塊模式來編寫代碼
(function(window, undefined){
})(window);
4.2 聲明一個變量taskList,用來存儲需要隊列信息
(function(window, undefined){
var taskList = [];
})(window);
隊列中,單個項的存儲設計為一個json,存儲需要觸發的消息,以及方法執行時需要的參數列表。比如LazyMan('Hank'),需要的存儲信息如下。
{
'msg':'LazyMan',
'args':'Hank'
}
當執行LazyMan方法的時候,調用訂閱方法,將需要執行的信息存入taskList中,緩存起來。
存儲的信息,會先保留著,等發布方法進行提取,執行和輸出。
4.3 訂閱方法
訂閱方法的調用方式設計:subscribe("lazyMan", "Hank")
(function(window, undefined){
var taskList = [];
// 訂閱
function subscribe(){
var param = {},
args = Array.prototype.slice.call(arguments);
if(args.length < 1){
throw new Error("subscribe 參數不能為空!");
}
param.msg = args[0]; // 消息名
param.args = args.slice(1); // 參數列表
if(param.msg == "sleepFirst"){
taskList.unshift(param);
}else{
taskList.push(param);
}
}
})(window);
用一個param變量來組織好需要存儲的信息,然后push進taskList中,緩存起來。
特別的,如果是sleepFirst,則放置在隊列頭部。
4.4 發布方法
(function(window, undefined){
var taskList = [];
// 訂閱方法 代碼...
// 發布
function publish(){
if(taskList.length > 0){
run(taskList.shift());
}
}
})(window);
將隊列中的存儲信息讀取出來,交給run方法(暫定,后續實現)
去執行。這里限定每次發布只執行一個,以維持隊列里面的方法可以挨個執行。
另外,這里使用shift()方法的原因是,取出一個,就在隊列中刪除這一個,避免重復執行。
4.5 實現LazyMan類
// 類
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;
};
將LazyMan類實現,具有eat、sleep、sleepFrist等行為。
觸發一次行為,就在taskList中記錄一次,并返回當前對象,以支持鏈式調用。
4.6 實現輸出console.log的包裝方法
// 輸出文字
function lazyManLog(str){
console.log(str);
}
為什么還要為console.log包裝一層,是因為在實戰項目中,產經經常會修改輸出提示的UI。如果每一處都用console.log直接調用,那改起來就麻煩很多。
另外,如果要兼容IE等低級版本瀏覽器,也可以很方便的修改。
也就是DRY原則(Don't Repeat Youself)。
4.7 實現具體執行的方法
// 具體方法
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);
}
這里的重點是解決setTimeout執行時會延遲調用,也即線程異步執行的問題。只有該方法執行成功后,再發布一次消息publish()
,提示可以執行下一個隊列信息。否則,就會一直等待。
4.8 實現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:;
}
}
這個方法有點像鴨式辨型接口,所以注釋叫鴨子叫
。
run方法接收隊列中的單個消息,然后讀取出來,看消息是什么類型的,然后執行對應的方法。
4.9 暴露接口LazyMan,讓外部可以調用
(function(window, undefined){
// 很多代碼...
// 暴露接口
window.LazyMan = function(str){
subscribe("lazyMan", str);
setTimeout(function(){
publish();
}, 0);
return new LazyMan();
};
})(window);
接口LazyMan里面的publish方法必須使用setTimeout進行調用。這樣能讓publish()
執行的線程延后,掛起。等鏈式方法都執行完畢后,線程空閑下來,再執行該publish()
。
另外,這是一個對外接口,所以調用的時候,同時也會new 一個新的LazyMan,并返回,以供調用。
五、總結
1. 好處
使用觀察者模式,讓代碼可以解耦到合理的程度,使后期維護更加方便。
比如我想修改eat
方法,我只需要關注eat()
和LazyMan.prototype.eat
的實現。其他地方,我都可以不用關注。這就符合了最少知識原則。
2. 不足
LazyMan.prototype.eat
這種方法的參數,其實可以用arguments代替,我沒寫出來,怕弄得太復雜,就留個優化點吧。
使用了unshift和shift方法,沒有考慮到低版本IE瀏覽器的兼容。
六、完整源碼和線上demo
完整源碼已經放在我的gitHub上
源碼入口:https://github.com/wall-wxk/blogDemo/blob/master/2017/01/22/lazyMan.html
demo訪問地址:https://wall-wxk.github.io/blogDemo/2017/01/22/lazyMan.html
demo需要打開控制臺,在控制臺中調試代碼。
七、番外
網上有人也實現了lazyMan,但是實現的方式我不是很喜歡和認同,但是也是一種思路,這里順便貼出來給大伙看看。
如何實現一個LazyMan:http://web.jobbole.com/89626/
閱讀原文:www.jianshu.com/p/f1b7cb456d37
文章列表