摘要:用原生寫一個(gè)多動(dòng)癥的簡(jiǎn)歷預(yù)覽地址源碼地址最近在知乎上看到方應(yīng)杭用寫了一個(gè)會(huì)動(dòng)的簡(jiǎn)歷,覺(jué)得挺好玩的,研究一下其實(shí)現(xiàn)思路,決定試試用原生來(lái)實(shí)現(xiàn)。
用原生js寫一個(gè)"多動(dòng)癥"的簡(jiǎn)歷
預(yù)覽地址
源碼地址
會(huì)動(dòng)的簡(jiǎn)歷實(shí)現(xiàn)思路最近在知乎上看到@方應(yīng)杭用vue寫了一個(gè)會(huì)動(dòng)的簡(jiǎn)歷,覺(jué)得挺好玩的,研究一下其實(shí)現(xiàn)思路,決定試試用原生js來(lái)實(shí)現(xiàn)。
這張會(huì)動(dòng)的簡(jiǎn)歷,就好像一個(gè)打字員在不斷地錄入文字,頁(yè)面呈現(xiàn)動(dòng)態(tài)效果。又好像一個(gè)早已經(jīng)錄制好影片,而我們只是坐在放映機(jī)前觀看。
原理分兩個(gè)部分
頁(yè)面能看見(jiàn)的不斷跳動(dòng)著的增加的文字,由innerHTML控制
頁(yè)面的布局效果由藏在"背后的"style標(biāo)簽完成
想象一下你要往一張網(wǎng)頁(yè)每間隔0.1秒增加一個(gè)啊字,是不是開個(gè)定時(shí)器,間斷地往body里面塞啊,就可以?。](méi)錯(cuò),做到這一步就完成了原理的第一部分
再想象一下,在往頁(yè)面里面塞啊的時(shí)候,我還想改變啊字的字體顏色以及網(wǎng)頁(yè)背景顏色,那應(yīng)該怎么做呢,是不是執(zhí)行下面的代碼就可以呢,沒(méi)錯(cuò),只不過(guò)更改字體和背景色不是突然改變的,而是也是開個(gè)定時(shí)器,間斷地往style標(biāo)簽中塞入以下代碼,這樣就完成了原理的第二步,是不是好簡(jiǎn)單 ???, 接下來(lái)讓我們一步步完成它
.xxx{ color: blue; background: red; }項(xiàng)目搭建
在這個(gè)項(xiàng)目中我們
使用webpack2來(lái)完成項(xiàng)目的構(gòu)建
使用yarn來(lái)處理依賴包的管理
使用es6的寫法
使用部分原生dom操作api
standard.js(代碼風(fēng)格約束利器)
目錄結(jié)構(gòu)如下
最重要的幾個(gè)模塊分別是resumeEditor(簡(jiǎn)歷編輯模塊) 、 stylesEditor(簡(jiǎn)歷樣式編輯模塊) 、 以及vQuery(封裝的dom操作模塊)
最后app.js(入口模塊)再將幾個(gè)模塊的功能結(jié)合起來(lái)完成整個(gè)項(xiàng)目。
因?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)查看不同的解決方案
master(使用回調(diào)函數(shù)處理)
promise(使用promise處理)
generator-thunk(使用generator + thunk函數(shù)處理)
generator-promise(使用generator + promise處理)
async(使用async處理)
showStyles(0, () => { showResume(() => { showStyles(1, () => { markdownToHtml(() => { showStyles(2) }) }) }) })解決回調(diào)地獄之promise
回調(diào)方式能夠解決異步操作問(wèn)題,但是代碼寫起來(lái)非常的不美觀,可讀性差,代碼呈橫向發(fā)展趨勢(shì)...偉大的程序員們開疆?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ù)牡胤?,歡迎大家指正。???,最后點(diǎn)個(gè)贊,點(diǎn)個(gè)star好不好呀。
源碼地址
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82816.html
摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠?lái)都是中的主導(dǎo)范式。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠?lái)都是JavaScript中的主導(dǎo)范式。JavaScript作為一門多范式編程語(yǔ)言,然而,近幾年,函數(shù)式編程越來(lái)越多得受到開發(fā)者的青睞。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。因此,...
摘要:雖然有了十全的計(jì)劃,但如何高效率去記住上面那么多東西是一個(gè)大問(wèn)題,看看我是怎么做的。 前言 前一篇文章講述了我在三月份毫無(wú)準(zhǔn)備就去面試的后果,一開始心態(tài)真的爆炸,但是又不服氣,一想到每次回來(lái)后家人朋友問(wèn)我面試結(jié)果的期待臉,越覺(jué)得必須付出的行動(dòng)來(lái)證明自己了。 面經(jīng)傳送門:一個(gè)1年工作經(jīng)驗(yàn)的PHP程序員是如何被面試官虐的? 下面是我花費(fèi)兩個(gè)星期做的準(zhǔn)備,主要分三部分: 有計(jì)劃——計(jì)劃好...
摘要:拿到秋招的同學(xué),如確定入職需與用人單位簽署三方協(xié)議,以保證雙方的利益不受損失。當(dāng)然每個(gè)崗位所要求的側(cè)重點(diǎn)不同,但卻百變不離其宗。方法論要想達(dá)成某個(gè)目標(biāo)都有其特定的方法論,學(xué)習(xí)技術(shù)也不例外,掌握適當(dāng)?shù)膶W(xué)習(xí)方法才能事半功倍。 寫在前面的話 筆者從17年的2月份開始準(zhǔn)備春招,其中遇到不少坑,也意識(shí)到自己走過(guò)的彎路。故寫了這篇文章總結(jié)一番,本文適合主動(dòng)學(xué)習(xí)的,對(duì)自己要學(xué)的課程不明確的,對(duì)面試有...
摘要:拿到秋招的同學(xué),如確定入職需與用人單位簽署三方協(xié)議,以保證雙方的利益不受損失。當(dāng)然每個(gè)崗位所要求的側(cè)重點(diǎn)不同,但卻百變不離其宗。方法論要想達(dá)成某個(gè)目標(biāo)都有其特定的方法論,學(xué)習(xí)技術(shù)也不例外,掌握適當(dāng)?shù)膶W(xué)習(xí)方法才能事半功倍。 寫在前面的話 筆者從17年的2月份開始準(zhǔn)備春招,其中遇到不少坑,也意識(shí)到自己走過(guò)的彎路。故寫了這篇文章總結(jié)一番,本文適合主動(dòng)學(xué)習(xí)的,對(duì)自己要學(xué)的課程不明確的,對(duì)面試有...
閱讀 2712·2021-10-12 10:12
閱讀 2343·2021-09-02 15:41
閱讀 2576·2019-08-30 15:55
閱讀 1409·2019-08-30 13:05
閱讀 2442·2019-08-29 11:21
閱讀 3542·2019-08-28 17:53
閱讀 3034·2019-08-26 13:39
閱讀 808·2019-08-26 11:50