综述:这是一道经典的面试题,综合考察面试者多方面的能力,走向高级前端之路必备,主要考察的点有:
1.方法链式调用 2.类的使用和面向对象编程的思路 3.设计模式的应用,(发布-订阅者模式(也叫观察者模式)的应用) 4.代码的解耦 5.最少知识原则,也即迪米特法则(Law of Demeter),代码解耦,一个函数执行一个单一的功能, 6.代码的书写结构和命名
1.题目如下
实现一个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
以此类推。
第一种方法,比较好维护,不便于理解:
(function(window,undefined){
var taskList = [];
// 类
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 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);
}
}
// 发布者模式
function publish(){
if(taskList.length > 0){
run(taskList.shift());//获取到当前事件队列中的第一个函数并执行
}
}
// 鸭子叫
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:;
}
}
// 具体方法
function lazyMan(str){
console.log("Hi!This is "+ str +"!");
publish();
}
function eat(str){
console.log("Eat "+ str +"~");
publish();
}
function sleep(num){
setTimeout(function(){
console.log("Wake up after "+ num);
publish();
}, num*1000);
}
function sleepFirst(num){
setTimeout(function(){
console.log("Wake up after "+ num);
publish();
}, num*1000);
}
// 暴露接口
window.LazyMan = function(str){
subscribe("lazyMan", str);
setTimeout(function(){
publish();
}, 0);
return new LazyMan();
};
})(window);
/**
*time/author:2019/4/19 "mouyao"
*desc:三种调用方式如下,感受promise的代码编写逻辑
*/
/* LazyMan("Hank").sleep(10).eat("dinner");
LazyMan("Hank").eat("dinner").eat("supper");
LazyMan("Hank").sleepFirst(5).eat("supper")*/
第二种实现方法:
这种代码更加便于理解,但是并不利于后期的维护。
/**
*time/author:2019/4/19 "mouyao"
*desc:另一种实现方式,这种方式更加容易理解,只是代码可维护性降低了
*/
function _LazyMan(name){
this.tasks = [];
var self = this;
var fn =(function(n){
var name = n;
return function(){
console.log("Hi! This is " + name + "!");
self.next();
}
})(name);
this.tasks.push(fn);
setTimeout(function(){
self.next();
}, 0); // 在下一个事件循环启动任务
}
/* 事件调度函数 */
_LazyMan.prototype.next = function() {
var fn = this.tasks.shift();
fn && fn();
};
_LazyMan.prototype.eat=function(name){
var self = this;
//这种实现方法的核心点在这里,被使用了很多次,这里相当于是一个微任务,将这个函数存储到变量中,当需要使用的时候调用即可
var fn =(function(name){
return function(){
console.log("Eat " + name + "~");
self.next()
}
})(name);
this.tasks.push(fn);
return this; // 实现链式调用
};
_LazyMan.prototype.sleep=function(time){
var self = this;
var fn = (function(time){
return function() {
setTimeout(function(){
console.log("Wake up after " + time + "s!");
self.next();
}, time * 1000);
}
})(time);
this.tasks.push(fn);
return this;
};
_LazyMan.prototype.sleepFirst = function(time) {
var self = this;
var fn = (function(time) {
return function() {
setTimeout(function() {
console.log("Wake up after"+time+"s!");
self.next();
},time*1000);
}
})(time);
this.tasks.unshift(fn);
return this;
};
/* 封装 */
function LazyMan(name){
return new _LazyMan(name);
}
/*
LazyMan("Hank").sleep(2).eat("dinner");
LazyMan("Hank").eat("dinner").eat("supper");*/
LazyMan("Hank").sleepFirst(5).eat("supper")