用原生js寫一個(gè)"多動(dòng)癥"的簡(jiǎn)歷

用原生js寫一個(gè)"多動(dòng)癥"的簡(jiǎn)歷

預(yù)覽地址
源碼地址

最近在知乎上看到@方應(yīng)杭用vue寫了一個(gè)會(huì)動(dòng)的簡(jiǎn)歷墨缘,覺(jué)得挺好玩的撮躁,研究一下其實(shí)現(xiàn)思路尘奏,決定試試用原生js來(lái)實(shí)現(xiàn)马昨。

http://odssgnnpf.bkt.clouddn.com/return.gif

會(huì)動(dòng)的簡(jiǎn)歷實(shí)現(xiàn)思路

這張會(huì)動(dòng)的簡(jiǎn)歷,就好像一個(gè)打字員在不斷地錄入文字虽界,頁(yè)面呈現(xiàn)動(dòng)態(tài)效果汽烦。又好像一個(gè)早已經(jīng)錄制好影片,而我們只是坐在放映機(jī)前觀看莉御。

原理分兩個(gè)部分

  1. 頁(yè)面能看見(jiàn)的不斷跳動(dòng)著的增加的文字撇吞,由innerHTML控制
  2. 頁(yè)面的布局效果由藏在"背后的"style標(biāo)簽完成

想象一下你要往一張網(wǎng)頁(yè)每間隔0.1秒增加一個(gè)字俗冻,是不是開(kāi)個(gè)定時(shí)器,間斷地往body里面塞梢夯,就可以啊晴圾!沒(méi)錯(cuò)颂砸,做到這一步就完成了原理的第一部分

再想象一下,在往頁(yè)面里面塞的時(shí)候死姚,我還想改變啊字的字體顏色以及網(wǎng)頁(yè)背景顏色人乓,那應(yīng)該怎么做呢,是不是執(zhí)行下面的代碼就可以呢都毒,沒(méi)錯(cuò)色罚,只不過(guò)更改字體和背景色不是突然改變的,而是也是開(kāi)個(gè)定時(shí)器账劲,間斷地往style標(biāo)簽中塞入以下代碼戳护,這樣就完成了原理的第二步,是不是好簡(jiǎn)單 ??????瀑焦, 接下來(lái)讓我們一步步完成它

.xxx{
  color: blue;
  background: red; 
}

項(xiàng)目搭建

在這個(gè)項(xiàng)目中我們

  1. 使用webpack2來(lái)完成項(xiàng)目的構(gòu)建
  2. 使用yarn來(lái)處理依賴包的管理
  3. 使用es6的寫法
  4. 使用部分原生dom操作api
  5. standard.js(代碼風(fēng)格約束利器)

目錄結(jié)構(gòu)如下

目錄結(jié)構(gòu)

最重要的幾個(gè)模塊分別是resumeEditor(簡(jiǎn)歷編輯模塊) 腌且、 stylesEditor(簡(jiǎn)歷樣式編輯模塊)以及vQuery(封裝的dom操作模塊)
最后app.js(入口模塊)再將幾個(gè)模塊的功能結(jié)合起來(lái)完成整個(gè)項(xiàng)目榛瓮。

vQuery(封裝的dom操作模塊)

因?yàn)楹竺娴膸讉€(gè)模塊都要依賴這個(gè)小模塊铺董,所以我們先簡(jiǎn)單的看下。

class Vquery {
  constructor (selector, context) {
    this.elements = getEles(selector, context)
  }

  optimizeCb (callback) {
    ...
  }

  get (index) {
    ...
  }

  html (sHtml) {
    ...
  }

  addClass (iClass) {
    ...
  }

  css (styles) {
    ...
  }

  height (h) {
    ...
  }

  scrollTop (top) {
    ...
  }
}

export default (selector, context) => {
  return new Vquery(selector, context)
}


可以看出它做的事就是封裝一個(gè)構(gòu)造函數(shù)Vquery禀晓,它的實(shí)例會(huì)有一些簡(jiǎn)單的dom操作方法精续,最后為了能夠像jQuery那樣使用$().funcName的形式去使用,我們導(dǎo)出了一個(gè)匿名函數(shù)粹懒,在匿名函數(shù)中去new Vquery

stylesEditor(簡(jiǎn)歷樣式編輯模塊)

簡(jiǎn)歷所展現(xiàn)的布局效果都是由這個(gè)模塊完成的,核心方法是showStyles重付。

const showStyles = (num, callback) => {
  let style = styles[num]
  let length
  let prevLength

  if (!style) {
    return
  }

  length = styles.filter((item, i) => { // 計(jì)算數(shù)組styles前n個(gè)元素的長(zhǎng)度
    return i <= num
  }).reduce((result, item) => {
    result += item.length
    return result
  }, 0)

  prevLength = length - style.length

  clearInterval(timer)
  timer = setInterval(() => {
    let start = currentStyle.length - prevLength
    let char = style.substring(start, start + 1) || ''
    currentStyle += char
    if (currentStyle.length === length) { // 數(shù)組styles前n個(gè)元素已經(jīng)全部塞入,則關(guān)閉定時(shí)器凫乖,并且執(zhí)行外面?zhèn)鬟M(jìn)來(lái)的回調(diào)堪夭,進(jìn)而執(zhí)行下一步操作
      clearInterval(timer)
      callback && callback()
    } else {
      let top = $stylePre.height() - MAX_HEIGHT
      if (top > 0) { // 當(dāng)塞入的內(nèi)容已經(jīng)超過(guò)了容器的高度,我們需要設(shè)置一下滾動(dòng)距離才方便演示接下來(lái)的內(nèi)容
        goBottom(top)
      }
      $style.html(currentStyle)
      $stylePre.html(Prism.highlight(currentStyle, Prism.languages.css))
    }
  }, delay)
}


stylesEditor(簡(jiǎn)歷樣式編輯模塊)

簡(jiǎn)歷編輯模塊用來(lái)展示簡(jiǎn)歷內(nèi)容拣凹,主要會(huì)經(jīng)歷由markdown格式往html頁(yè)面形式的轉(zhuǎn)換森爽。

const markdownToHtml = (callback) => {
  $resumeMarkdown.css({
    display: 'none'
  })
  $resumeWrap.addClass(iClass)
  $resumetag.html(marked(resumeMarkdown)) // 借助marked工具將markdown轉(zhuǎn)化為html
  callback && callback() // 執(zhí)行后續(xù)的回調(diào)
}

const showResume = (callback) => { // 原理基本上同stylesEditor, 不斷地往簡(jiǎn)歷編輯的容器中塞入事先準(zhǔn)備好的簡(jiǎn)歷內(nèi)容嚣镜,當(dāng)全部塞入的時(shí)候再關(guān)閉定時(shí)器爬迟,并執(zhí)行后續(xù)的回調(diào)操作
  clearInterval(timer)
  timer = setInterval(() => {
    currentMarkdown += resumeMarkdown.substring(start, start + 1)
    if (currentMarkdown.length === length) {
      clearInterval(timer)
      callback && callback()
    } else {
      $resumeMarkdown.html(currentMarkdown)
      start++
    }
  }, delay)
}

app(入口模塊)

最后由app入口模塊將以上幾個(gè)模塊整合完成項(xiàng)目的功能,我們找出其中的核心代碼來(lái), ??菊匿,你沒(méi)看錯(cuò)付呕,傳說(shuō)中的回調(diào)地獄计福,亮瞎了我的狗眼啊。想必大家和我一樣都是不愿意看到這坨惡心的代碼的徽职,但對(duì)于處理異步問(wèn)題象颖,回調(diào)又的確是一直以來(lái)的解決方案之一。

因?yàn)槎〞r(shí)器的操作是異步行為姆钉,而我們的簡(jiǎn)歷生成過(guò)程會(huì)涉及到多個(gè)異步操作说订,所以為了看到如首頁(yè)預(yù)覽鏈接的效果,必須等前一個(gè)步驟完成之后潮瓶,才能執(zhí)行下一步步驟陶冷,這里首先使用的回調(diào)函數(shù)的解決方案,大家可以從github上拉取代碼毯辅,分別切換以下幾個(gè)分支來(lái)查看不同的解決方案

  1. master(使用回調(diào)函數(shù)處理)
  2. promise(使用promise處理)
  3. generator-thunk(使用generator + thunk函數(shù)處理)
  4. generator-promise(使用generator + promise處理)
  5. async(使用async處理)
showStyles(0, () => {
  showResume(() => {
    showStyles(1, () => {
      markdownToHtml(() => {
        showStyles(2)
      })
    })
  })
})


解決回調(diào)地獄之promise

回調(diào)方式能夠解決異步操作問(wèn)題埂伦,但是代碼寫起來(lái)非常的不美觀,可讀性差思恐,代碼呈橫向發(fā)展趨勢(shì)...偉大的程序員們開(kāi)疆?dāng)U土發(fā)明了promise的解決方案沾谜。我們來(lái)看一下promise分支中app模塊最終的寫法

showStylesWrap(0)
  .then(showResumeWrap)
  .then(showStylesWrap.bind(null, 1))
  .then(markdownToHtmlWrap)
  .then(showStylesWrap.bind(null, 2))

可以看到,代碼清爽了很多胀莹,縱向發(fā)展类早,應(yīng)用第一步第二步第三步...一眼就能夠看出來(lái),當(dāng)然實(shí)現(xiàn)的邏輯是將原來(lái)的相關(guān)的模塊用Promise包裝起來(lái)嗜逻,并且在原來(lái)回調(diào)函數(shù)執(zhí)行的地方resolve即可涩僻,詳細(xì)實(shí)現(xiàn),歡迎查看項(xiàng)目源碼

解決回調(diào)地獄之generator-thunk栈顷,generator-promise

兩種方式比較類似逆日,都要用到es6中的generator。關(guān)于什么是generator萄凤,thunk函數(shù)室抽,可以查看軟大神關(guān)于ECMAScript 6 入門,這里簡(jiǎn)要地講述一下,其如何處理異步操作問(wèn)題使得可以將異步行為寫起來(lái)如同步般爽靡努。

function timeOut1 () {
  setTimeout(() => {
    console.log(1111)
  }, 1000)
}

function timeOut2 () {
  setTimeout(() => {
    console.log(2222)
  }, 200)
}

function * gen () {
  yield timeOut1()
  yield timeOut2()
}

let g = gen()
g.next()
g.next()

上面的代碼在過(guò)了200毫秒會(huì)log出2222坪圾,過(guò)了1秒鐘之后log出1111

這,要??了惑朦,你不是說(shuō)generator寫起來(lái)同步可以解決異步問(wèn)題嗎兽泄,為毛這里timeOut2沒(méi)有在timeOut1之后執(zhí)行呢,畢竟gen函數(shù)中看起來(lái)是希望這樣的嘛漾月。

其實(shí)不然病梢,timeOut2啥時(shí)候執(zhí)行取決于

g.next()
g.next()

試想兩個(gè)函數(shù)幾乎同時(shí)執(zhí)行,那在定時(shí)器中當(dāng)然是200毫秒后的timeOut2先打印出2222來(lái),但是有沒(méi)有辦法蜓陌,讓timeOut2在timeOut1后執(zhí)行呢觅彰?答案是有的

function timeOut1 () {
  setTimeout(() => {
    console.log(1111)
    g.next()
  }, 1000)
}

function timeOut2 () {
  setTimeout(() => {
    console.log(2222)
  }, 200)
}

function * gen () {
  yield timeOut1()
  yield timeOut2()
}

let g = gen()
g.next()

可以看到我們?cè)趖imeOut1執(zhí)行完成之后,再將指針指向下一個(gè)位置钮热,即timeOut2再去執(zhí)行填抬,這樣的結(jié)果就和gen函數(shù)中兩個(gè)yield的寫起來(lái)同步感覺(jué)一樣了。但是含有一個(gè)問(wèn)題隧期,如果涉及到很多個(gè)異步操作飒责,我們是很難通過(guò)上面的方式將異步流程管理起來(lái)的。于是我們需要做下面一件事

function co (fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next); // thunk和promise不同地方之一在這里厌秒, promise是result.value.then(next)
  }

  next();
}

內(nèi)部的next函數(shù)就是 thunk 的回調(diào)函數(shù)读拆。next函數(shù)先將指針移到 generator 函數(shù)的下一步(gen.next方法)擅憔,然后判斷 generator 函數(shù)是否結(jié)束(result.done屬性)鸵闪,如果沒(méi)結(jié)束,就將next函數(shù)再傳入 thunk 函數(shù)(result.value屬性)暑诸,否則就直接退出蚌讼。

最后我們?cè)诳匆幌峦ㄟ^(guò)co函數(shù)的寫法完成上面的例子

function timeOut1() {
  return (callback) => {
    setTimeout(() => {
      console.log(1111)
      callback()
    }, 1000)
  }

}

function timeOut2() {
  return (callback) => {
    setTimeout(() => {
      console.log(2222)
      callback()
    }, 200)
  }
}

function co(fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next); // thunk和promise不同地方之一在這里, promise是result.value.then(next)
  }

  next();
}

co(function * () {
  yield timeOut1()
  yield timeOut2()
})


解決回調(diào)地獄之a(chǎn)sync

async其實(shí)就是generator函數(shù)的語(yǔ)法糖个榕。大家如果把generator弄明白了篡石,使用它一定不再話下,關(guān)于這個(gè)項(xiàng)目的用法西采,歡迎查看async分支源代碼凰萨,這里不再贅述。

尾述

本文中可能存在闡述不當(dāng)?shù)牡胤叫倒荩瑲g迎大家指正胖眷。??????,最后點(diǎn)個(gè)贊霹崎,點(diǎn)個(gè)star好不好呀珊搀。
源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尾菇,隨后出現(xiàn)的幾起案子境析,更是在濱河造成了極大的恐慌,老刑警劉巖派诬,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劳淆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡默赂,警方通過(guò)查閱死者的電腦和手機(jī)憔儿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)放可,“玉大人谒臼,你說(shuō)我怎么就攤上這事朝刊。” “怎么了蜈缤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵拾氓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我底哥,道長(zhǎng)咙鞍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任趾徽,我火速辦了婚禮续滋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘孵奶。我一直安慰自己疲酌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布了袁。 她就那樣靜靜地躺著朗恳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪载绿。 梳的紋絲不亂的頭發(fā)上粥诫,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音崭庸,去河邊找鬼怀浆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛怕享,可吹牛的內(nèi)容都是我干的执赡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼熬粗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搀玖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起驻呐,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤灌诅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后含末,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體猜拾,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年佣盒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挎袜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖盯仪,靈堂內(nèi)的尸體忽然破棺而出紊搪,到底是詐尸還是另有隱情,我是刑警寧澤全景,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布耀石,位于F島的核電站,受9級(jí)特大地震影響爸黄,放射性物質(zhì)發(fā)生泄漏滞伟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一炕贵、第九天 我趴在偏房一處隱蔽的房頂上張望梆奈。 院中可真熱鬧,春花似錦称开、人聲如沸亩钟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)径荔。三九已至督禽,卻和暖如春脆霎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狈惫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工睛蛛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胧谈。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓忆肾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親菱肖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子客冈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 異步編程對(duì)JavaScript語(yǔ)言太重要。Javascript語(yǔ)言的執(zhí)行環(huán)境是“單線程”的稳强,如果沒(méi)有異步編程场仲,根本...
    呼呼哥閱讀 7,311評(píng)論 5 22
  • 最開(kāi)始是想將《用原生js寫一個(gè)"多動(dòng)癥"的簡(jiǎn)歷》這篇文章里的代碼全部講一遍的,但是在讀這這段代碼的時(shí)候退疫,從中學(xué)習(xí)到...
    我愛(ài)薩摩耶閱讀 532評(píng)論 0 0
  • 本文首發(fā)在個(gè)人博客:http://muyunyun.cn/posts/7b9fdc87/ 提到 Node.js, ...
    牧云云閱讀 1,684評(píng)論 0 3
  • 弄懂js異步 講異步之前渠缕,我們必須掌握一個(gè)基礎(chǔ)知識(shí)-event-loop。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,711評(píng)論 0 5
  • 四年了 那次以后再也沒(méi)有如此敞開(kāi)過(guò)心扉 今夜開(kāi)放 卻無(wú)疾而終
    小肚腩喃閱讀 84評(píng)論 0 0