摘要:應(yīng)用需要正確并合適響應(yīng)各種網(wǎng)絡(luò)請(qǐng)求用戶操作計(jì)時(shí)事件和各種延時(shí)動(dòng)作。好了,我們的新流程圖畫(huà)出來(lái)了提示城市名稱(chēng)查找不是很復(fù)雜,谷歌地圖為此提供了非常簡(jiǎn)單的。形象點(diǎn)表示就是,函數(shù)是一等公民。
原文
當(dāng)你將關(guān)注點(diǎn)從樣式,美學(xué)和網(wǎng)格系統(tǒng)轉(zhuǎn)移到邏輯,框架和編寫(xiě)JavaScript代碼時(shí)。一切都開(kāi)始了,你會(huì)發(fā)現(xiàn)你處于你的web開(kāi)發(fā)歷程中最激動(dòng)人心的那一刻。
在這個(gè)非常時(shí)刻你會(huì)發(fā)現(xiàn),當(dāng)涉及到JS時(shí),它不僅僅是幾個(gè)簡(jiǎn)單的jQuery技巧和視覺(jué)效果。你的視野是一整個(gè)web應(yīng)用,而不再僅僅是局限于頁(yè)面。
當(dāng)你把更多的精力投入到寫(xiě)js代碼時(shí),你會(huì)開(kāi)始考慮交互、你的子模塊和邏輯。事情開(kāi)始奏效,你感覺(jué)到你的app有了生命。一個(gè)全新的、令人興奮的世界出現(xiàn)在你眼前,同樣,也出現(xiàn)了很多全新的、棘手的問(wèn)題。
你并不氣餒,并想出來(lái)各種各樣的辦法,代碼也寫(xiě)的越來(lái)越多。嘗試某些博客文章中那各種各樣的技術(shù),不斷地完善自己解決問(wèn)題的方法。
然后,你開(kāi)始覺(jué)得有些不對(duì)路。
你的腳本文件慢慢變大,一小時(shí)前才200行的,現(xiàn)在已經(jīng)500行了?!昂佟薄阆搿斑@沒(méi)什么大不了的”。隨后,你開(kāi)始閱讀關(guān)于代碼維護(hù)的相關(guān)文章,并著手實(shí)現(xiàn)它。開(kāi)始分離你的邏輯代碼,并把它們分塊、組件。事情開(kāi)始又變好了點(diǎn)。代碼像圖書(shū)館藏書(shū)那樣分類(lèi)存放。你感覺(jué)良好,因?yàn)楦鞣N各樣的文件被以正確的命名放置在合適的目錄里。代碼變得模塊化,更易于維護(hù)了。
然而,你又感覺(jué)不對(duì)路了,但是不知道哪里有問(wèn)題。
web應(yīng)用的行為很少是線性的。事實(shí)上,web應(yīng)用的許多行為應(yīng)該是瞬時(shí)發(fā)生(有時(shí)候應(yīng)該是出乎意料或是自發(fā)地)。
應(yīng)用需要正確并合適響應(yīng)各種網(wǎng)絡(luò)請(qǐng)求、用戶操作、計(jì)時(shí)事件和各種延時(shí)動(dòng)作。名為“異步”和“race condition”的怪物無(wú)時(shí)不刻在敲你的腦門(mén)。
你需要將你帥氣的模塊化結(jié)構(gòu)與丑陋的新娘結(jié)合 - 異步代碼。一個(gè)棘手的問(wèn)題來(lái)了:我應(yīng)該把這段代碼放在哪里?
你會(huì)把你的app精心地劃分成一個(gè)個(gè)構(gòu)建塊。導(dǎo)航和內(nèi)容組件被整齊地放置在合適的目錄中,較小的輔助腳本文件包含了執(zhí)行普通任務(wù)的重復(fù)代碼。一切都通過(guò)app.js這個(gè)文件來(lái)調(diào)度,一切都從這里開(kāi)始。完美。
但是,你的目標(biāo)是在app中的某個(gè)地方調(diào)用異步代碼,運(yùn)行后把它放在一旁。
異步代碼應(yīng)該放在ui組件么?或者放在主文件里?app的哪個(gè)構(gòu)建塊負(fù)責(zé)響應(yīng)呢?哪一個(gè)構(gòu)建塊負(fù)責(zé)開(kāi)始運(yùn)行?錯(cuò)誤處理呢?你在腦海里考慮著各種方法——但是你還是愁眉不解——你意識(shí)到如果想要拓展或維護(hù)這些代碼,那難度是相當(dāng)大的,問(wèn)題還沒(méi)解決。你需要理想的一勞永逸的方案。
放松一下,這對(duì)你來(lái)說(shuō)沒(méi)有問(wèn)題。事實(shí)上,你的思維越有條理,這種煩惱就會(huì)越強(qiáng)烈。
你開(kāi)始閱讀有關(guān)處理此問(wèn)題的信息并尋求即用型解決方案。一開(kāi)始,你了解到promises優(yōu)于回調(diào)的地方。隨后,你開(kāi)始試圖了解什么是RxJS(并且為什么網(wǎng)上的一些人說(shuō)這是解決網(wǎng)絡(luò)異步請(qǐng)求的唯一解決方案)。經(jīng)過(guò)一些閱讀之后,你試著去理解,為什么一個(gè)博客寫(xiě)道沒(méi)有redux-thunk的redux沒(méi)有意義,但是另一個(gè)人認(rèn)為redux-saga也是如此。
一天結(jié)束后,你疲憊的大腦充斥著各種詞。閱讀完大量可行的方法后,你的想法噴涌出來(lái)。為什么會(huì)有這么多呢?那么復(fù)雜?人們?cè)趺聪矚g在互聯(lián)網(wǎng)上爭(zhēng)論,不去開(kāi)發(fā)一個(gè)好的模式?
因?yàn)檫@些都不重要
無(wú)論使用哪種框架,異步代碼都不可能被正確地存放好。并沒(méi)有一個(gè)單一、通用、既定的解決方案,要根據(jù)具體的開(kāi)發(fā)環(huán)境、需求來(lái)采取不同的方案。
并且,這篇文章也不會(huì)提供解決所有問(wèn)題的方案。但是它可以給你提供一個(gè)好的思路,讓你處理好你的異步代碼——因?yàn)樗蓟谝粋€(gè)非?;镜脑瓌t。
從某些角度來(lái)看,編程語(yǔ)言的結(jié)構(gòu)并不復(fù)雜。畢竟,它們只是類(lèi)似于計(jì)算機(jī)的愚蠢東西,能夠在各種盒子里儲(chǔ)存值而已,并且通過(guò)if或函數(shù)調(diào)用改變程序執(zhí)行流程。作為一種命令式和略微面向?qū)ο蟮恼Z(yǔ)言,js在這里也是類(lèi)似的。
這意味著究其本質(zhì),來(lái)自各路大神寫(xiě)的各種宇宙級(jí)異步庫(kù)(無(wú)論是redux-saga、RxJS、觀察者或者其他奇奇怪怪的庫(kù))都依賴(lài)相同的基本原理。它們并沒(méi)有那么神奇——它必須讓大家學(xué)習(xí)它的概念,這里并沒(méi)有新發(fā)明。
為什么這個(gè)事實(shí)如此重要?讓我們來(lái)考慮這樣的一個(gè)例子。
先來(lái)個(gè)簡(jiǎn)單的app,這個(gè)app可以讓我們?cè)诘貓D上標(biāo)記我們喜歡的地方。沒(méi)有什么花哨的東西:只是右側(cè)的地圖視圖和左側(cè)的簡(jiǎn)單側(cè)邊欄。單擊地圖應(yīng)在地圖上保存新標(biāo)記。
當(dāng)然,我們需要一個(gè)與眾不同的特性:我們需要它用local storage記住我們標(biāo)記好的地方列表。
綜上所述,我們可以畫(huà)一個(gè)流程圖出來(lái)
看,并不是很復(fù)雜
為簡(jiǎn)潔起見(jiàn),下面的示例將在不使用任何框架或UI庫(kù)的情況下編寫(xiě) - 僅涉及vanilla js。此外,我們將使用谷歌地圖API的一小部分 - 如果你想自己創(chuàng)建類(lèi)似的應(yīng)用程序,你應(yīng)該注冊(cè)你的API密鑰[https://cloud.google.com/maps...](https://cloud.google.com/maps...
).
快速分析一下
init方法用google地圖api初始化地圖組件,注冊(cè)地圖點(diǎn)擊事件并且嘗試從local storage加載數(shù)據(jù)。
addPlace方法處理地圖點(diǎn)擊事件——把新地點(diǎn)加在列表里并且更新ui
renderMarkers方法迭代地點(diǎn)列表,清除地圖后,將標(biāo)記放在其上。
忽略一些不完善的地方(沒(méi)有錯(cuò)誤處理之類(lèi)的)—— 它將作為原型提供足夠好的服務(wù)。完美。讓我們寫(xiě)一些html:
假設(shè)我們寫(xiě)了一些樣式(我們不會(huì)在這里介紹它,因?yàn)樗幌嚓P(guān)),不管你信不信 - 它實(shí)際上是這樣做的:
盡管它很丑,但是管用。不過(guò)可拓展性不好。
首先,我們的代碼責(zé)任分割不明確。如果你聽(tīng)說(shuō)過(guò)SOLID)原則,你應(yīng)該清楚我們已經(jīng)打破了第一條規(guī)則:?jiǎn)我回?zé)任原理。在我們的例子中——盡管很簡(jiǎn)單——一個(gè)js文件包含了所有,包括處理用戶響應(yīng)的代碼和數(shù)據(jù)轉(zhuǎn)換和異步代碼?!盀槭裁催@樣不好,運(yùn)行起來(lái)不是棒棒的么?”——你可能會(huì)這么說(shuō)。確實(shí)運(yùn)行起來(lái)棒棒的,但是如果要加新特性那就不棒棒了——可維護(hù)性低。
我用一個(gè)例子讓你徹底心服口服:
首先,我們想要側(cè)邊欄加標(biāo)記列表。第二,我們想要用googleAPI實(shí)現(xiàn)在地圖上看到城市名的功能——這就引入了異步代碼。
好了,我們的新流程圖畫(huà)出來(lái)了:
既然你調(diào)用別人的接口,那肯定不是同步代碼而是異步代碼啦。它首先要調(diào)用google的js庫(kù),并且回復(fù)過(guò)來(lái)需要一定時(shí)間。雖然有點(diǎn)復(fù)雜,但是用于教學(xué)剛剛好。
讓我們回到ui代碼這里并且這里有個(gè)明顯的事實(shí)。我們的頁(yè)面分兩大塊,側(cè)邊欄和主要內(nèi)容區(qū)。我們絕對(duì)不能把它們兩的代碼放在一起。原因很明顯——我們將來(lái)有四個(gè)組件怎么辦?六個(gè)呢?一百個(gè)呢?我們需要把我們的代碼分開(kāi)——我們需要有兩個(gè)獨(dú)立的js文件。一個(gè)是側(cè)邊欄,一個(gè)是主要內(nèi)容區(qū)塊。問(wèn)題來(lái)了,哪一個(gè)應(yīng)該存放地方標(biāo)記列表的數(shù)組呢?
哪一個(gè)正確呢?哪個(gè)都不對(duì)。還記得單一責(zé)任原則么?為了降低代碼冗余度,我們應(yīng)該以某種方式分離關(guān)注點(diǎn)并將我們的數(shù)據(jù)邏輯保存在其他地方??窗桑?/p>
代碼分離萬(wàn)金油:我們可以把進(jìn)行數(shù)據(jù)操作的代碼放到另一個(gè)文件里,這個(gè)文件集中處理數(shù)據(jù)。這個(gè)servce文件將負(fù)責(zé)那些與本地存儲(chǔ)同步的問(wèn)題和機(jī)制。相反,組件將僅僅提供接口。這符合SOLID原則。讓我們介紹下這個(gè)模式:
Service code Map component code Sidebar component code:好了,一個(gè)大問(wèn)題已經(jīng)解決。代碼整齊擺放在它們?cè)摯奈恢?。但在我們感覺(jué)良好之前,運(yùn)行下這個(gè)。
。。。oops。
在做任何動(dòng)作之后,app沒(méi)有交互了。
為什么? 好吧,我們沒(méi)有實(shí)現(xiàn)任何同步手段。使用導(dǎo)入的方法添加地點(diǎn)后,我們不會(huì)在任何地方發(fā)出任何信號(hào)。在調(diào)用addPlace()之后,我們甚至無(wú)法在下一步調(diào)用getPlaces()方法,因?yàn)槌鞘胁檎沂钱惒降?,需要時(shí)間來(lái)完成。
程序在后臺(tái)進(jìn)行,但是并沒(méi)有反應(yīng)到界面上——在地圖上添加標(biāo)記后,我們沒(méi)有看到側(cè)邊欄的更新。怎么解決?
一個(gè)簡(jiǎn)單的方法就是,使用定時(shí)器輪詢(xún)我們的服務(wù),例如:
它有用么?emm。。有,但不是最佳方案。大多數(shù)情況下我們并不需要這個(gè)服務(wù)。
畢竟,你也不會(huì)定時(shí)去看你的包裹有沒(méi)到達(dá)。同樣地,如果你把汽車(chē)丟去維修,你也不會(huì)每半小時(shí)給修車(chē)師傅打電話詢(xún)問(wèn)工作是否完成(至少希望你不是這種人)。正常的情況應(yīng)該是這樣的,修車(chē)師傅修好了,自然會(huì)打電話給你。當(dāng)然,我們事先留電話了。
現(xiàn)在,我們?cè)趈s中嘗試下這種“留電話”的方式。
js是一門(mén)非常神奇的語(yǔ)言——它的一個(gè)古怪的特征就是可以把函數(shù)視為其他值。形象點(diǎn)表示就是,“函數(shù)是一等公民”。這意味著任何函數(shù)都可以分配給變量或作為參數(shù)傳遞給另一個(gè)函數(shù)。事實(shí)上你已經(jīng)接觸過(guò)了:還記得setTimeout,setInterval和各種事件監(jiān)聽(tīng)器回調(diào)嗎? 它們通過(guò)將函數(shù)作為參數(shù)來(lái)使用。
這種特性在異步場(chǎng)景中是基礎(chǔ)我們可以定義一個(gè)更新我們的UI的函數(shù) - 然后將它傳遞給另一部分的代碼,在那里它將被調(diào)用。
使用這種機(jī)制,我們可以將renderCities方法以某種方式傳遞給dataService。在那里,它將在必要時(shí)被調(diào)用:畢竟,服務(wù)能準(zhǔn)確地知道何時(shí)應(yīng)該將數(shù)據(jù)傳輸?shù)浇M件。
試一試,我們首先在服務(wù)端添加這個(gè)功能,然后在某個(gè)時(shí)刻調(diào)用它。
現(xiàn)在,在sidebar那里使用
你知道會(huì)發(fā)生什么么?當(dāng)在加載我們的sidebar代碼時(shí),它在dataService注冊(cè)了renderCities方法。
在這種情況下,當(dāng)我們的數(shù)據(jù)發(fā)生更改時(shí),dataService就會(huì)調(diào)用此函數(shù)(由于addPlace()的調(diào)用)。
確切地說(shuō),我們的代碼的一部分是事件的SUBSCRIBER,另一部分是PUBLISHER(服務(wù)方法)。我們已經(jīng)實(shí)現(xiàn)了發(fā)布 - 訂閱模式的最基本形式,這是幾乎所有高級(jí)異步概念的基本概念。
還有呢?
請(qǐng)注意,我們的代碼,僅限于一個(gè)監(jiān)聽(tīng)組件(即,一位訂閱者)。如果其他方法也用了這個(gè)subscribe方法來(lái)傳遞的話,它會(huì)覆蓋掉dataService的changeListener變量,為了解決這個(gè)問(wèn)題,我們需要用數(shù)組來(lái)存儲(chǔ)監(jiān)聽(tīng)者。
現(xiàn)在,我們可以稍微整理一下代碼并編寫(xiě)一個(gè)函數(shù)來(lái)為我們調(diào)用所有的監(jiān)聽(tīng)者:
這樣我們也可以連接map.js組件,以便它對(duì)服務(wù)中的所有操作做出正確的反應(yīng):
如果需要傳遞參數(shù)怎么辦?我們可以使用監(jiān)聽(tīng)者的參數(shù)直接獲得。像這樣:
然后,可以輕松地在組件中檢索數(shù)據(jù):
這里還有更多的可能性 - 我們可以為不同類(lèi)型的行為創(chuàng)建不同的主題(或渠道)。此外,我們可以提取發(fā)布和訂閱方法到一個(gè)文件并從那里使用它。但就目前而言,還OK啦 - 以下是使用我們剛剛創(chuàng)建的相同代碼的應(yīng)用的簡(jiǎn)短視頻
(譯者注,大家去原文那里看吧)
(譯者注:接下來(lái)的內(nèi)容是作者關(guān)于這個(gè)模式的想法,他說(shuō),那些組件的概念比如RxjS,雖然它們功能更強(qiáng)大、概念更加地復(fù)雜,但是基本概念都是上文講過(guò)的。它們搞得太復(fù)雜了而已。并且這個(gè)模式也可以套在其他的地方。如DOM操作。另外,本文只是講了最基本的,還有很多地方可以拓展。比如取消訂閱、事件訂閱等等。最后作者還建議我們多點(diǎn)搞優(yōu)秀的源代碼,down下來(lái)用debugger研究源碼。挖掘出它們最基本的思想。多動(dòng)手、多思考,不要害怕專(zhuān)有名詞,覺(jué)得很高大上、很難理解。其實(shí)就是那么一回事。有些人搞得太復(fù)雜了。)
(譯者為什么不翻譯完呢?因?yàn)橄胱x者們自己嘗試去翻譯,最重要的原因,是因?yàn)樽g者懶。。。)
Does this whole publish-subscribe thing resemble something you might already know? After giving it some thought, it’s the pretty same mechanism that you use in element.addEventListener(action, callback). You subscribe your function to a particular event, which ich being called when some action is published by element. Same story.
Going back to the title: why is this thing so bloody important? After all, in the long run, there is little sense in holding up to vanilla JavaScript and modifying the DOM manually?—?same goes with manual mechanisms for passing and receiving events. Various frameworks have their established solutions: Angular uses RxJS, React have state and props management with possibility of boosting it with redux, literally every usable framework or library have its own method of data synchronization.
Well, the truth is that all of them use some variation of publish-subscribe pattern.
As we already said?—?DOM event listeners are nothing more than subscribing to publishing UI actions. Going further: what is a Promise? From certain point of view, it’s just a mechanism that allows us to subscribe for completion of a certain deferred action, then publishes some data when ready.
React state and props change? Components’ updating mechanisms are subscribed to the changes. Websocket’s on()? Fetch API? They allow to subscribe to certain network action. Redux? It allows to subscribe to changes in the store. And RxJS? It’s a shameless one big subscribe pattern.
It’s the same principle. There are no magic unicorns under the hood. It’s just like the ending of the Scooby-Doo episode.
It’s not a great discovery. But it’s important to know:
No matter what method of solving asynchronous problem will you use, it will be always some variation of the same principle: something subscribes, something publishes.
That’s why it is so essential. You can always think of publish and subscribe. Take note and keep going. Keep building larger and more complex application with many asynchronous mechanisms?—?and no matter how difficult it may look like, try to synchronize everything with publishers and subscribers.
Still, there is a number of topics untouched in this story:
Mechanisms of unsubscribing listeners when not needed anymore,
Multi-topic subscribing (just like addEventListener allows you to subscribe to different events),
Expanded ideas: event buses, etc.
To expand your knowledge, you can review a number of JavaScript libraries that implement publish-subscribe in its bare form:
https://github.com/mroderick/...
https://github.com/Sahadar/pu...
https://github.com/shystruk/p...
Go ahead and try to use them, break them and run the debugger in order to see what happens under the hood. Also, there is a number of great articles that describe this idea very well.
You can find the code from this story in the following GitHub repository:
https://github.com/hzub/pubsu...
Keep experimenting and tinkering—and don’t be afraid of the buzz words, they’re usually just regular code in disguise. And keep thinking.
See you!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/98865.html
摘要:主題來(lái)自于的典型面試問(wèn)題列表。有多種方法來(lái)處理事件委托。這種方法的缺點(diǎn)是父容器的偵聽(tīng)器可能需要檢查事件來(lái)選擇正確的操作,而元素本身不會(huì)是一個(gè)監(jiān)聽(tīng)器。 showImg(http://fw008950-flywheel.netdna-ssl.com/wp-content/uploads/2014/11/Get-Hired-Fast-How-to-Job-Search-Classifieds...
摘要:忍者級(jí)別的函數(shù)操作對(duì)于什么是匿名函數(shù),這里就不做過(guò)多介紹了。我們需要知道的是,對(duì)于而言,匿名函數(shù)是一個(gè)很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個(gè)供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...
摘要:結(jié)合與的發(fā)布訂閱模式實(shí)踐本文初衷最近恰好在公司做了一個(gè)聊天室系統(tǒng),所以在系統(tǒng)中做了一下對(duì)進(jìn)行的化改造,所以想寫(xiě)篇文章總結(jié)一下,如果大家有什么更好的方法或者心得感悟,歡迎交流技術(shù)??紤]到對(duì)并沒(méi)什么本質(zhì)影響,所以本文就不涉及了業(yè)務(wù)場(chǎng)景基于的聊天 結(jié)合promise與websocket的發(fā)布/訂閱模式實(shí)踐 本文初衷 最近恰好在公司做了一個(gè)聊天室系統(tǒng),所以在系統(tǒng)中做了一下對(duì)websocket進(jìn)...
摘要:擁抱異步編程縱觀發(fā)展史也可以說(shuō)成開(kāi)發(fā)的發(fā)展史,你會(huì)發(fā)現(xiàn)異步徹底改變了這場(chǎng)游戲。可以這么說(shuō),異步編程已成為開(kāi)發(fā)的根基。這也是你應(yīng)盡早在上投入大量時(shí)間的一處核心知識(shí)點(diǎn),這其中包含和等重要概念。這也是最突出的一項(xiàng)貢獻(xiàn)。 原文地址:Medium - Learning How to Learn JavaScript. 5 recommendations on how you should spend ...
摘要:是目前唯一一個(gè)支持同步調(diào)用的跨平臺(tái)年度上最多的個(gè)項(xiàng)目前端掘金年接近尾聲,在最近的幾篇文章中,會(huì)整理總結(jié)一些年度開(kāi)源項(xiàng)目。 JS 全棧教程 - 前端 - 掘金本課程是基于阮一峰的 js 全棧教程的視頻版本,免費(fèi)供大家觀看... 2016 年 10 個(gè)最佳的 CodePen 作品 - 前端 - 掘金說(shuō)到 CodePen,前端開(kāi)發(fā)者們肯定不會(huì)陌生。如果說(shuō) Dribbble 是設(shè)計(jì)師們聚集的圣...
閱讀 2502·2021-11-23 09:51
閱讀 2544·2021-11-11 17:21
閱讀 3173·2021-09-04 16:45
閱讀 2464·2021-08-09 13:42
閱讀 2293·2019-08-29 18:39
閱讀 2966·2019-08-29 14:12
閱讀 1359·2019-08-29 13:49
閱讀 3437·2019-08-29 11:17