工作室面试学妹的时候想的一些东东
题目
打字机
基础要求
用 JavaScript 实现类似打字机打字的效果,能在页面上显示出来。
做完上面之后的要求
打字机每一次的敲击汇聚起来就像一道流,每隔一段时间触发的定时器事件汇聚成一道流,两道流汇聚起来就形成了定时发送某些文本的流。
封装一个能定时发送文本的流,并且能对这个流发出的文本做一系列的处理,最后能根据处理之后的文本触发某些事件。
抽象 ⬇️
Stream => message => (optional) handler pipeline => subscribe events
举例 ⬇️
createStream => message => mapMessageToSomething => renderMessageToView
实现上
学妹写的基础要求没啥问题呐,逻辑很清晰,也确实能够工作。
// 大致的逻辑,差不多的伪代码
string = `something`;
function typing() {
getElement;
getSubstringOfString;
setTimeout(typing);
renderSubstringToDOM;
}
typing();
但是问题就在于刚刚好能够工作
。
举个例子,比如现在,我不想把发射出来的文本 仅仅只是显示在页面上了;
我有一个能根据输入的自然语言,返回相应的回应的 AI,然后将 AI 返回的话语显示到页面上。
string = `something`;
function typing() {
getElement;
getSubstringOfString;
setTimeout(typing);
// the following code has been changed
feedSubstringToAI;
thenFetchResponseDataFromAI;
renderFeedbackFromAIToDOM;
}
typing();
感觉还好,勉强来说逻辑还是能看懂的,对吧。
但是随着需求变得越来越复杂,代码也会成比例地膨胀。
有人可能会说,这有啥可怕的呐,我把这些代码提取到几个函数里面,命名清晰不就好啦。
string = `something`;
function typing() {
// the following code has been changed
doSomethingBefore();
getSubstring(string);
setTimeout(typing);
doSomethingElseAfter();
}
// balabala... number of functions are ommited
typing();
可能顺着函数看下去还是能理解逻辑的,但是觉得还是不是很好看。(我脑子全是面向过程的思维)
我觉得,团队中写代码,最重要的是你写的东西别人能看懂,能和你合作。
所以代码可读性
是十分十分十分重要的(重要的事情要说三次)。
思考
回到最开始的我在问题中提到的。
抽象 ⬇ ️
Stream => message => (optional) handler pipeline => subscribe events
观察这个流程,然后再结合学妹一开始的代码,可以发现,她的代码正好可以划分成几块。
string = `something`;
function typing() {
// `Stream` to feed message
getSubstringOfString;
setTimeout(typing);
// end of `Stream` part
// `subscribe` message stream to do something
getElement; // 这一行我移动了一下
renderSubstringToDOM;
// end of `subscribe events`
}
typing();
也就是说我们其实可以按一开始的抽象的流程,把各部分模块的代码抽出来。
setTimeOut
和 string
是用来构建 Stream
的
getSubstringOfString
这其实也可以说是属于 handler pipeline
这一系列对流做处理的函数的逻辑
getElement
、renderSubstringToDOM
是属于 subscribe events
部分的
// 根据这样的抽象,可以将代码各模块的逻辑隔离。
// 实现大概像如下一个效果(跟学妹的代码的逻辑不太一样)
const scan = initial => x => (initial += x);
const message = `Just For Fun!`;
const eachLetterSpeackTwice =
createMessageStream(message, 300) // Stream
.pipe( // handler pipeline
message => message + message,
scan(`Let me say something: `)
);
eachLetterSpeackTwice
.subscribe(x => console.log(x)); // subscribe events
/* 运行结果
node test.js
Let me say something: JJ
Let me say something: JJuu
Let me say something: JJuuss
Let me say something: JJuusstt
Let me say something: JJuusstt
Let me say something: JJuusstt ff
Let me say something: JJuusstt ffoo
Let me say something: JJuusstt ffoorr
Let me say something: JJuusstt ffoorr
Let me say something: JJuusstt ffoorr FF
Let me say something: JJuusstt ffoorr FFuu
Let me say something: JJuusstt ffoorr FFuunn
Let me say something: JJuusstt ffoorr FFuunn!!
*/
这样各模块的逻辑就各司其职,不会把各模块的逻辑堆在一起,要修改、要增添某个部分的内容也很方便
代码的可读性和可维护性也会 O**K 啦。
实现这个效果的玩具代码 ⬇️ :
// 我不写注释会被打么嘻嘻嘻
const createMessageStream = (message, itv) => {
// get next value and done sign
const next = ((str) => {
let currentIndex = 0;
const maxLength = str.length;
return () => {
if (currentIndex < maxLength) {
return { value: str[currentIndex++], done: false }
} else {
str = message = itv = null;
return { value: null, done: true }
}
}
})(message);
return {
pipe(...pipeFns) {
return {
subscribe(...subscribedFns) {
// it's inert, it sit there until `subscribe` to them
const wrappedFn = () => {
// pipeline to modify the output message
let { value: pipedMessage, done } = next();
if (!done) {
pipeFns.forEach(fn => (pipedMessage = fn(pipedMessage)));
// execute subscribe events with the message
subscribedFns.forEach(fn => fn(pipedMessage));
setTimeout(wrappedFn, itv);
}
};
setTimeout(wrappedFn, itv);
}
};
},
subscribe(...fn) {
this.pipe().subscribe(...fn);
}
};
};
后话
有人可能会觉得这个效果有点像某个玩意(RxJS :你在说啥???),其实也就是最近又试了一下 RxJS 然后想到的题目,就让学妹试着玩了一下。
主要是觉得这种组织代码的方式有点意思 – 万物基于流,对流进行处理,然后对流最终输出的东西触发相应的事件。
代码不是很健壮,只是为了演示这个概念。
This blog is under a CC BY-NC-SA 4.0 Unported License
Link to this article: http://nhh.ink/2018/10/30/diary-30-10-2018/