LazyMan深入解析和实现
一、题目介绍
以下是我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
LazyMan深入解析和实现的更多相关文章
- 微信LazyMan笔试题的深入解析和实现
一.题目介绍 以下是我copy自网上的面试题原文: 实现一个LazyMan,可以按照以下方式调用: LazyMan("Hank")输出: Hi! This is Hank! ...
- LazyMan的深入解析和实现
一.题目介绍 以下是我copy自网上的面试题原文: 实现一个LazyMan,可以按照以下方式调用: LazyMan("Hank")输出: Hi! This is Hank! L ...
- 面试题-lazyMan实现
原文:如何实现一个LazyMan 面试题目 实现一个LazyMan,可以按照以下方式调用: LazyMan('Hank'),输出: Hi, This is Hank! LazyMan('Hank'). ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- .NET Core中的认证管理解析
.NET Core中的认证管理解析 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...
- Html Agility Pack 解析Html
Hello 好久不见 哈哈,今天给大家分享一个解析Html的类库 Html Agility Pack.这个适用于想获取某网页里面的部分内容.今天就拿我的Csdn的博客列表来举例. 打开页面 用Fir ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
随机推荐
- HoloLens开发手记 - HoloLens真机上手简评
千呼万唤始出来,终于今天拿到了HoloLens真机. 使用体验 使用自带的应用录制了一段使用视频,如下 设备概览 包装盒 本体 试戴 实际效果 GalaxyExplorer试玩 全息图像贴到现实场景表 ...
- windows下更新node环境
https://github.com/Kenshin/gnvm 下载gnvm.exe程序 使用where node命令查看node所在目录,并将下载好的gnvm.exe程序复制到目录中 输入gnvm ...
- 本地k8s环境minikube搭建过程
首先要安装docker这个环境是需要自己安装的.相关步骤如下: 1 2 3 4 5 6 7 8 9 10 11 yum install -y yum-utils device-mapper-persi ...
- Android 内存溢出解决方案(OOM) 整理总结<转>
在最近做的工程中发现加载的图片太多或图片过大时经常出现OOM问题,找网上资料也提供了很多方法,但自己感觉有点乱,特此,今天在不同型号的三款安卓手机上做了测试,因为有效果也有结果,今天小马就做个详细的总 ...
- 【杂谈】对IO与NIO的认识
IO流与NIO块的数据缓存 Java的IO是面向流设计的,通常我们通过IO流读取数据,只能指定读取数据的大小,而不能选择数据读取的起始位置.数据就像流水一样,流过我们的应用,一旦流过就无法回头.除非我 ...
- github总结(3)--怎样在github上面实现页面托管
part Ⅱ:gh-pages分支--实现页面托管 第一步:在github线上创建一个新的仓库 第二步:在项目本地地址,git init初始化,再git add --all进行文档追踪添加 第三步:g ...
- PHP打印指定日期
打印某一日期的前一天 echo date("Y-m-d",(strtotime("2009-01-01") - 3600*24)); (1)打印明天此时的时间戳 ...
- Java基础之Java 修饰符
前言:Java内功心法之Java 修饰符,看完这篇你向Java大神的路上又迈出了一步(有什么问题或者需要资料可以联系我的扣扣:734999078) Java语言提供了很多修饰符,主要分为以下两类: 访 ...
- Docker入门之一Docker在Window下安装
最近这几年,各个大公司都在打造自己的云平台,什么阿里云,华为云,腾讯云等等云,以及各种的微服务架构,其实在这当中Docker容器技术算是一个很重要的角色. 一.下载 在下载之前首先检查一下自己的电脑是 ...
- /proc文件系统(一):cpuinfo
0. 前言 /proc 文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间. 它以文件系统的方式为内核与进程提供通信的接口.用户和应用程序可以通过/proc得到系统的信息,并可以改变内核的某 ...