微信LazyMan笔试题的深入解析和实现,LazyMan的深入
分类:前端技术

微信LazyMan笔试题的深入解析和实现

2017/02/03 · JavaScript · Javascript, 异步

原文出处: wall_wxk   

以下是我copy自网上的面试题原文:

如何实现一个 LazyMan?

2016/12/24 · JavaScript · 9 评论 · Javascript, 异步

本文作者: 伯乐在线 - Natumsol 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

3月份找实习的时候,微信面试官给了我一套笔试题,今天整理时无意中翻了出来,其中有一道题特别有意思:

实现一个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

以此类推。

这是典型的JavaScript流程控制,问题的关键是如何实现任务的顺序执行。在Express有一个类似的东西叫中间件,这个中间件和我们这里的吃饭、睡觉等任务很类似,每一个中间件执行完成后会调用next()函数,这个函数用来调用下一个中间件。

对于这个问题,我们也可以利用相似的思路来解决,首先创建一个任务队列,然后利用next()函数来控制任务的顺序执行:

JavaScript

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); }

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
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);
}

打赏支持我写出更多好文章,谢谢!

打赏作者

一、题目介绍

以下是我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

以此类推。

实现一个LazyMan,可以按照以下方式调用:LazyMan输出:Hi! This is Hank! LazyMan.sleep.eat输出Hi! This is Hank!//等待10秒..Wake up after 10Eat dinner~ LazyMan.eat.eat输出Hi This is Hank!Eat dinner~Eat supper~ LazyMan.sleepFirst.eat输出//等待5秒Wake up after 5Hi This is Hank!Eat supper 以此类推。

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

图片 1 图片 2

2 赞 11 收藏 9 评论

二、题目考察的点

先声明:我不是微信员工,考察的点是我推测的,可能不是,哈哈!

1.方法链式调用
2.类的使用和面向对象编程的思路
3.设计模式的应用
4.代码的解耦
5.最少知识原则,也即 迪米特法则(Law of Demeter)
6.代码的书写结构和命名

先声明:我不是微信员工,考察的点是我推测的,可能不是,哈哈!

关于作者:Natumsol

图片 3

阿里巴巴 前端工程师 个人主页 · 我的文章 · 5 ·    

图片 4

三、题目思路解析

1.看题目输出示例,可以确定这是拟人化的输出,也就是说:应该编写一个类来定义一类人,叫做LazyMan。可以输出名字、吃饭、睡觉等行为。
2.从输出的句子可以看出,sleepFrist的优先级是最高的,其他行为的优先级一致。
3.从三个例子来看,都得先调用LazyMan来初始化一个人,才能继续后续行为,所以LazyMan是一个接口。
4.句子是按调用方法的次序进行顺序执行的,是一个队列。

1.方法链式调用 2.类的使用和面向对象编程的思路 3.设计模式的应用 4.代码的解耦 5.最少知识原则,也即 迪米特法则(Law of Demeter) 6.代码的书写结构和命名

四、采用观察者模式实现代码

4.1 采用模块模式来编写代码

JavaScript

(function(window, undefined){ })(window);

1
2
3
(function(window, undefined){
 
})(window);

4.2 声明一个变量taskList,用来存储需要队列信息

JavaScript

(function(window, undefined){ var taskList = []; })(window);

1
2
3
(function(window, undefined){
    var taskList = [];
})(window);

队列中,单个项的存储设计为一个json,存储需要触发的消息,以及方法执行时需要的参数列表。比如LazyMan(‘Hank’),需要的存储信息如下。

JavaScript

{ 'msg':'LazyMan', 'args':'Hank' }

1
2
3
4
{
    'msg':'LazyMan',
    'args':'Hank'
}

当执行LazyMan方法的时候,调用订阅方法,将需要执行的信息存入taskList中,缓存起来。
存储的信息,会先保留着,等发布方法进行提取,执行和输出。

4.3 订阅方法

订阅方法的调用方式设计:subscribe("lazyMan", "Hank")

JavaScript

(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);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(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 发布方法

JavaScript

(function(window, undefined){ var taskList = []; // 订阅方法 代码... // 发布 function publish(){ if(taskList.length > 0){ run(taskList.shift()); } } })(window);

1
2
3
4
5
6
7
8
9
10
11
12
(function(window, undefined){
    var taskList = [];
 
        // 订阅方法 代码...
 
    // 发布
    function publish(){
        if(taskList.length > 0){
            run(taskList.shift());
        }
    }
})(window);

将队列中的存储信息读取出来,交给run方法(暂定,后续实现)去执行。这里限定每次发布只执行一个,以维持队列里面的方法可以挨个执行。
另外,这里使用shift()方法的原因是,取出一个,就在队列中删除这一个,避免重复执行。

4.5 实现LazyMan类

JavaScript

// 类 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; };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 类
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的包装方法

JavaScript

// 输出文字 function lazyManLog(str){ console.log(str); }

1
2
3
4
// 输出文字
function lazyManLog(str){
    console.log(str);
}

为什么还要为console.log包装一层,是因为在实战项目中,产经经常会修改输出提示的UI。如果每一处都用console.log直接调用,那改起来就麻烦很多。
另外,如果要兼容IE等低级版本浏览器,也可以很方便的修改。
也就是DRY原则(Don’t Repeat Youself)。

4.7 实现具体执行的方法

JavaScript

// 具体方法 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); }

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
// 具体方法
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方法,用于识别要调用哪个具体方法,是一个总的控制台

JavaScript

// 鸭子叫 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:; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
// 鸭子叫
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,让外部可以调用

JavaScript

(function(window, undefined){ // 很多代码... // 暴露接口 window.LazyMan = function(str){ subscribe("lazyMan", str); setTimeout(function(){ publish(); }, 0); return new LazyMan(); }; })(window);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(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.看题目输出示例,可以确定这是拟人化的输出,也就是说:应该编写一个类来定义一类人,叫做LazyMan。可以输出名字、吃饭、睡觉等行为。 2.从输出的句子可以看出,sleepFrist的优先级是最高的,其他行为的优先级一致。 3.从三个例子来看,都得先调用LazyMan来初始化一个人,才能继续后续行为,所以LazyMan是一个接口。 4.句子是按调用方法的次序进行顺序执行的,是一个队列。

五、总结

1. 好处

使用观察者模式,让代码可以解耦到合理的程度,使后期维护更加方便。
比如我想修改eat方法,我只需要关注eat()LazyMan.prototype.eat的实现。其他地方,我都可以不用关注。这就符合了最少知识原则

2. 不足
LazyMan.prototype.eat这种方法的参数,其实可以用arguments代替,我没写出来,怕弄得太复杂,就留个优化点吧。
使用了unshift和shift方法,没有考虑到低版本IE浏览器的兼容。

4.1 采用模块模式来编写代码

六、完整源码和线上demo

完整源码已经放在我的gitHub上

源码入口:

demo访问地址:

demo需要打开控制台,在控制台中调试代码。

(function(window, undefined){});

七、番外

网上有人也实现了lazyMan,但是实现的方式我不是很喜欢和认同,但是也是一种思路,这里顺便贴出来给大伙看看。
如何实现一个LazyMan:

1 赞 收藏 评论

图片 5

4.2 声明一个变量taskList,用来存储需要队列信息

(function(window, undefined){ var taskList = [];});

队列中,单个项的存储设计为一个json,存储需要触发的消息,以及方法执行时需要的参数列表。比如LazyMan,需要的存储信息如下。

{ '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; // 参数列表 if(param.msg == "sleepFirst"){ taskList.unshift; }else{ taskList.push; } }});

用一个param变量来组织好需要存储的信息,然后push进taskList中,缓存起来。 特别的,如果是sleepFirst,则放置在队列头部。

4.4 发布方法

(function(window, undefined){ var taskList = []; // 订阅方法 代码... // 发布 function publish(){ if(taskList.length > 0){ run(taskList.shift;

将队列中的存储信息读取出来,交给run方法``去执行。这里限定每次发布只执行一个,以维持队列里面的方法可以挨个执行。 另外,这里使用shift()方法的原因是,取出一个,就在队列中删除这一个,避免重复执行。

4.5 实现LazyMan类

// 类function LazyMan(){};LazyMan.prototype.eat = function{ subscribe("eat", str); return this;};LazyMan.prototype.sleep = function{ subscribe("sleep", num); return this;};LazyMan.prototype.sleepFirst = function{ subscribe("sleepFirst", num); return this;};

将LazyMan类实现,具有eat、sleep、sleepFrist等行为。 触发一次行为,就在taskList中记录一次,并返回当前对象,以支持链式调用。

4.6 实现输出console.log的包装方法

// 输出文字function lazyManLog{ console.log;}

为什么还要为console.log包装一层,是因为在实战项目中,产经经常会修改输出提示的UI。如果每一处都用console.log直接调用,那改起来就麻烦很多。 另外,如果要兼容IE等低级版本浏览器,也可以很方便的修改。 也就是DRY原则(Don't Repeat Youself)。

4.7 实现具体执行的方法

// 具体方法function lazyMan{ lazyManLog("Hi!This is "  str  "!"); publish();}function eat{ lazyManLog("Eat "  str  "~"); publish();}function sleep{ setTimeout(function(){ lazyManLog("Wake up after "  num); publish(); }, num*1000); }function sleepFirst{ setTimeout(function(){ lazyManLog("Wake up after "  num); publish(); }, num*1000);}

这里的重点是解决setTimeout执行时会延迟调用,也即线程异步执行的问题。只有该方法执行成功后,再发布一次消息publish(),提示可以执行下一个队列信息。否则,就会一直等待。

4.8 实现run方法,用于识别要调用哪个具体方法,是一个总的控制台

// 鸭子叫function run{ var msg = option.msg, args = option.args; switch{ 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{ subscribe("lazyMan", str); setTimeout(function(){ publish; return new LazyMan;

接口LazyMan里面的publish方法必须使用setTimeout进行调用。这样能让publish()执行的线程延后,挂起。等链式方法都执行完毕后,线程空闲下来,再执行该publish()。 另外,这是一个对外接口,所以调用的时候,同时也会new 一个新的LazyMan,并返回,以供调用。

1. 好处

使用观察者模式,让代码可以解耦到合理的程度,使后期维护更加方便。 比如我想修改eat方法,我只需要关注eat()LazyMan.prototype.eat的实现。其他地方,我都可以不用关注。这就符合了最少知识原则

2. 不足 LazyMan.prototype.eat这种方法的参数,其实可以用arguments代替,我没写出来,怕弄得太复杂,就留个优化点吧。 使用了unshift和shift方法,没有考虑到低版本IE浏览器的兼容。

完整源码已经放在我的gitHub上

源码入口:

demo访问地址:

demo需要打开控制台,在控制台中调试代码。

网上有人也实现了lazyMan,但是实现的方式我不是很喜欢和认同,但是也是一种思路,这里顺便贴出来给大伙看看。如何实现一个LazyMan:

喜欢我文章的朋友,扫描以下二维码,关注我的个人技术博客,我的技术文章会第一时间在博客上更新点击链接wall的个人博客

图片 6wall的个人博客

本文由pc28.am发布于前端技术,转载请注明出处:微信LazyMan笔试题的深入解析和实现,LazyMan的深入

上一篇:JS相关概念,操功成本到底高在哪里 下一篇:没有了
猜你喜欢
热门排行
精彩图文
  • 运动端适配方案,多终端的适配实施方案
    运动端适配方案,多终端的适配实施方案
    移动端适配方案(下) 2017/01/25 · CSS ·移动端 本文作者: 伯乐在线 -risker。未经作者许可,禁止转载! 欢迎加入伯乐在线 专栏作者。 上一篇介绍了像素和视
  • 内部存储器分析工具简要介绍,内部存款和储蓄
    内部存储器分析工具简要介绍,内部存款和储蓄
    4类 JavaScript 内存泄漏及如何避免 2016/05/26 · JavaScript· 1 评论 ·内存泄漏 本文由 伯乐在线 -涂鸦码龙翻译。未经许可,禁止转载! 英文出处:SebastiánPeyro
  • HTML也可以静态编译,损害了复用性
    HTML也可以静态编译,损害了复用性
    React.Component 损害了复用性? 2016/09/07 · 底蕴技能 ·binding.scala,data-binding,React,scala.js 本文笔者: 伯乐在线 -ThoughtWorks。未经笔者许可,防止转发! 接待插足
  • 品质的法门
    品质的法门
    9 种改革 AngularJS 品质的艺术 2017/07/20 · JavaScript· AngularJS 初藳出处: JustinSpencer   译文出处:oschina    AngularJS 是当下利用非常遍布的 web app应用框架,随
  • 高质量滚动,实例解析防抖动和节流阀
    高质量滚动,实例解析防抖动和节流阀
    实例解析防抖动和节流阀 2016/04/26 · JavaScript· DOM 本文由 伯乐在线 -涂鸦码龙翻译。未经许可,幸免转发! 立陶宛共和国(Republic of Lithuania卡塔尔语出处: