工作室面试学妹的时候想的一些东东

题目

打字机

基础要求

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

也就是说我们其实可以按一开始的抽象的流程,把各部分模块的代码抽出来。

setTimeOutstring 是用来构建 Stream

getSubstringOfString 这其实也可以说是属于 handler pipeline 这一系列对流做处理的函数的逻辑

getElementrenderSubstringToDOM 是属于 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/