紅燈也可以掉頭?老司機都會犯傻!

3禁左即是禁止掉頭左轉與掉頭是有一定關係的,因為大多數情況下掉頭需要佔用左轉車道,所以禁止左轉意味着不能掉頭,除非該路口除了有“禁止左轉”的標誌外,還同時有“允許掉頭”的標誌,則可掉頭。下面這些情況可以掉頭1路口有掉頭標誌的可掉頭按照信號燈的指示進行掉頭,如果沒有信號燈,則根據路況有左轉標誌的,在不影響其他車輛通行或阻礙行人的情況進行掉頭,在這裏得提醒一下,有些掉頭車道是在最右側的。

今天小編開心的開着五菱宏光去兜風,準備右轉的時候在一個紅燈路口停了下來,後方的車輛不停的按着喇叭來催前面第一輛車輛,看來又是一個新手,不知道沒方向指示的紅燈是允許掉頭的。

但是今天我們討論的是紅燈能否掉頭的問題,有禁止標誌的路口大夥都看得懂,但是沒明確指示的就不一定了,一起來學習一下吧,剛拿到駕照的朋友再也不擔心給人家嗶嗶的催個不停了。

車輛掉頭規定

一開始我們先來看一下違章掉頭將會受到什麼樣的處罰,駕駛機動車違反禁令標誌、禁止、標線指示的,將一次記3分,部分地區還將罰200元。

禁止掉頭的九種情況

1

斑馬線處禁止掉頭

有些道路上設有斑馬線,雖然這一塊地方沒有明確禁止掉頭標識,但是機動車是不允許在斑馬線上掉頭的,就算在允許掉頭的路口,也要越過斑馬線才可以掉頭。

2

黃色實線禁止掉頭

在行至無“禁止掉頭”標誌的路口,是允許掉頭的,但是要注意道路中心線的虛實,如果是單黃實線或者雙黃實線都是禁止掉頭的。

3

禁左即是禁止掉頭

左轉與掉頭是有一定關係的,因為大多數情況下掉頭需要佔用左轉車道,所以禁止左轉意味着不能掉頭,除非該路口除了有“禁止左轉”的標誌外,還同時有“允許掉頭”的標誌,則可掉頭。

下面這些情況可以掉頭

1

路口有掉頭標誌的可掉頭

按照信號燈的指示進行掉頭,如果沒有信號燈,則根據路況有左轉標誌的,在不影響其他車輛通行或阻礙行人的情況進行掉頭,在這裏得提醒一下,有些掉頭車道是在最右側的。

2

黃色網格線可掉頭

黃色網格線大家可能都很清楚,就是嚴禁停車的意思,但在該區域內,只要沒有設置中間隔離護欄,是可以掉頭的,等同於“允許掉頭”的意思。

3

黃色虛實線可掉頭

如果是一虛一實的黃色線,是可以掉頭的,虛線一側車輛可向實線一側通行,實線一側的車輛是不允許向虛線一邊通行的。

總結:考試背得滾瓜爛熟的交規一畢業就給回教練了,加上一些馬路上交通標支模糊指引不明確,很容易造成新手犯錯,還會被後面車輛“嗶”個不停,希望通過這篇文章學習后,大家可以對掉頭的情況了如指掌。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

庇里牛斯山一頭棕熊被擊斃 引發起訴

摘錄自2020年6月11日法廣報導

庇里牛斯山脈原是熊的故鄉,後來熊幾乎絕跡,法國政府從1990年代開始引進棕熊計畫。從那時起,生態團體跟牧羊人團體便開始了反熊和護熊衝突,兩邊有時甚至暴力相向。目前庇里牛斯山中有大約50隻熊,6月9日星期二,在法國一側,庇里牛斯山脈的阿列日(Ariège)地方境內,一頭年輕的棕熊被發現中彈身亡。這一事件再次點燃生態主義者和畜養者的緊張關係。

法國生態轉型部長波恩(Élisabeth Borne)6月9日宣布了棕熊的死訊,她譴責這是一起需要狠狠譴責的非法殺熊事件,國家將就此案提出司法起訴。法國Saint-Girons地方憲兵研究隊正在對這起棕熊「謀殺案」展開調查,10日在圖盧茲市獸醫學校進行屍檢。Foix地方檢察官杜馬納(Laurent Dumaine)在新聞發布會上表示,兇手將以「未經允許摧毀受保護物種罪」,面臨3年監禁和15萬歐元罰款。

生態保育
生物多樣性
國際新聞
法國
生物多樣性
庇里牛斯山

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

專訪《不自然的同伴關係》作者克里斯提:為什麼「不要放養你家的貓」

環境資訊中心綜合外電;黃鈺婷 翻譯;林大利 審校;稿源:Mongabay

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

7500億歐元振興案 梅克爾籲歐盟7月底前達協議

摘錄自2020年6月18日中央社報導

德國總理梅克爾今(18日)敦促歐洲聯盟在7月底前,針對7500億歐元(8430億美元)振興方案達成協議,重振受武漢肺炎(COVID-19)疫情重創的歐洲經濟。

歐洲聯盟執行委員會(European Commission)主席范德賴恩提議(Ursula von der Leyen)設立這筆巨額基金,幫助歐盟走出有史以來最嚴重經濟衰退,但她的計畫立即遭到奧地利和荷蘭等財政立場保守成員國強烈反對。提議的振興方案內容包括援助款項5000億歐元和貸款2500億歐元,但需要歐盟成員國無異議通過。

歐洲理事會(European Council)預定6月19日舉行視訊峰會,討論這項引發爭議振興方案,這也是歐盟迄今規模最大經濟振興方案。不過,梅克爾表示,在歐盟各國領導人面對面會談前,預料不會做出決定。德國將於7月接掌歐盟輪值主席國。

國際新聞
歐洲
振興經濟
武漢肺炎
疫情
救經濟靠綠色振興

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

※台中搬家公司費用怎麼算?

生物多樣性提高100倍 生長快速的迷你森林風靡歐洲

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

疫情改變通勤方式 雅加達自行車需求看漲

摘錄自2020年06月21日中央通訊社印尼報導

受武漢肺炎疫情影響,可健身又可避開大眾運輸人潮的自行車成為通勤最佳選擇。雅加達省政府指出,疫情後自行車族增加,兩大主要幹道每天固定時段都會隔出臨時自行車道。

印尼第一大報羅盤報(Kompas)19日報導,雅加達使用中的自行車道只有25公里,仍在計劃、施工、驗收中的自行車道有63公里。雅加達自6月初逐漸鬆綁為控制2019冠狀病毒疾病(COVID-19,武漢肺炎)疫情實施的管制措施,民眾陸續恢復上班。

雅加達省政府交通局長夏孚林(Syafrin Liputo)指出,雅加達省政府近日已開始與警方合作,每天固定時段會在雅加達中區的蘇迪爾曼大道(Sudirman)和塔林大道(Thamrin)兩條主要幹道,使用路錐,隔出臨時自行車道,方便自行車族使用,全長約14公里。

自行車業者表示,疫情初期業績暴跌,最近業績突然成長,賣最好的是折疊式腳踏車,因為民眾勤於運動,需求大增,他要向供應商調貨也遇到供貨吃緊的問題。

生活環境
能源轉型
國際新聞
印尼
雅加達
自行車
武漢肺炎
疫情下的食衣住行
交通運輸

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

“節氣門”聽得多 “氣門”又是干什麼的?

而上述的16V發動機則是相對於之前8v發動機而言,一台8V的四缸發動機意味着每個氣缸兩個氣門,一個進氣門和一個排氣門,如果你現在在紙上畫一個圓,然後再從圓裏面取等大的圓,結果肯定是取的等大的圓越多,取出來總面積越大。

就在汽車越來越同質化的現在,連尾標也不如以前豐富,美國車競爭最激烈的時候,車內有一個CD機都要在尾標炫耀,而車主在路上碰到同款也會下意識看下他的尾標,是不是有帶電動車窗,現在除了花樣頗多的動力表示,僅僅只有各自的看家技術會單獨掛出來。

而國內汽車的尾標,說到當年愛麗舍上的16V尾標有多少朋友會有印象呢?

要是你當時還是個熊孩子可能會以為是一道沒出完的數學題。

要是你那會剛剛上了物理課,可能會以為這是代表着16V的電源。

其實這裏的V是氣門(valve)的簡稱。

我們都知道四衝程發動機的做工過程分別是吸氣,壓縮,做功,排氣。

從中也可以看出氣門的重要性,如果簡單把發動機比作人的話,那麼進氣量和排氣量就等同於我們人的肺活量,肺活量越高肯定運動能力更強。

像下面的入門玩家。

中等玩家。

高級玩家。

而上述的16V發動機則是相對於之前8v發動機而言,一台8V的四缸發動機意味着每個氣缸兩個氣門,一個進氣門和一個排氣門,如果你現在在紙上畫一個圓,然後再從圓裏面取等大的圓,結果肯定是取的等大的圓越多,取出來總面積越大。

所以單缸四氣門的“肺活量”肯定大於兩個氣門的發動機。

那照這樣說氣門越多越好咯,理論上是這樣,所以 以前還有單缸五個氣門的發動機。像法拉利的355。

其型號就表明了該身份,3.5L排量的發動機,每個氣缸5氣門。

而更接近生活的還有國內就有的捷達王20V(氣門為3進兩出)。

當時所有開過的人對這輛車的印象就是兩個字,好開。畢竟多了一肛。

說得那麼好為什麼現在基本上都是每個氣缸4氣門呢,捷達王的20V那麼好開為什麼沒有繼續發展呢?

我們下期再來聊發動機另外一個關鍵的東西,“凸輪軸“。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

這輛只要8萬起的車 頂級建築師和低調富二代都買

5L地球夢科技發動機,可以爆發出131匹強勁馬力,無論是起步加速還是中後段加速,全新鋒范的動力響應都表現的非常迅速,百公里10。2秒的加速時間更是在同級中名列前茅,滿足了年輕人的駕駛慾望。這樣的動力總成和強勁的加速性能和無論是對男主角時光,還是對很多追求速度的年輕人來說,都是很有吸引力的。

你難以靠近難以不再想念

我難以抗拒你容顏

把心畫在寫給你的信中

希望偶爾能夠見到你微笑的容顏

……

當這首熟悉的《難以抗拒你容顏》在電視里響起時,應該有無數人跟着哼唱了吧。沒錯,小編說的就是最近火爆的電視劇《咱們相愛吧》里女主之一蔡春娜在KTV里唱這首歌以至潸然淚下的劇情。這首歌勾起了她與劇中前男友的回憶,又何嘗沒有勾起大家對青春的回憶呢,簡直經典啊!所以這部劇紅也不是沒有道理,不僅身邊上至80歲跳廣場舞的阿姨下至18歲青春活潑的姑娘都沉迷其中,各大視頻網站也都佔據熱搜。

只見地鐵哪怕擠成狗,都有不少人堅決舉起手機在看;公司中午休息,女同事沉迷於劇情飯都顧不上吃;就連晚上跳廣場舞的大媽都提前結束回家追劇了。在好奇心的驅使下,小編也跟着看了起來。

而八點檔電視劇總有一種迷の魅力,一旦靠近就不可自拔。結果你們也猜到了,基本上是這樣的↓↓↓

但是!小編當然不是一個沉迷追劇不務正業的人,生活在哪裡汽車就在哪裡,電視看着看着就自然對裏面出現的汽車更關心一點,今天就來給大家八一八這部劇里出現頻率頗高的轎車——全新鋒范。

在劇里,它是男主角時光以及大毛的座駕,而兩位車主一個是著名的建築設計師,一個則是深藏不露的富二代,都不是“一般人”。 生活中小編身邊不少全新鋒范車主,也都是優質青年,可見這輛車對高品位人群的吸引力。

男主角時光作為一位建築設計師,對外觀的要求絕不會低。全新鋒范的外觀設計,恰恰符合了他的要求。飛翼式前臉突出凌厲與飽滿,車身上下雙腰線簡潔幹練,輪廓鮮明。翹起的尾部採用寬大設計,搭配鑽石切割的后尾燈組合,顯得動感又不失時尚。這種簡約不簡單的風格,與很多現代建築也是交相呼應的。

內飾未來感十足,布局層次分明。簡潔大氣的儀錶盤與中央的中控大屏、觸控式智能空調營造出科技感十足的駕駛氛圍,恍惚間會讓你覺得是在開一輛“未來之車”。

當然,全新鋒范的動力應該也是男主角時光選擇它的重要原因。這樣一個在生活中方方面面都追求完美的霸道總裁對動力也不會妥協。好在全新鋒范搭載的是本田1.5L地球夢科技發動機,可以爆發出131匹強勁馬力,無論是起步加速還是中後段加速,全新鋒范的動力響應都表現的非常迅速,百公里10.2秒的加速時間更是在同級中名列前茅,滿足了年輕人的駕駛慾望。這樣的動力總成和強勁的加速性能和無論是對男主角時光,還是對很多追求速度的年輕人來說,都是很有吸引力的。

而劇中另外一位開全新鋒范的主角大毛,一開始只是一個默默喜歡女主角芝芝的上班族,沒想到最後才知道,他的父親其實是有名的商人。

但大毛對此顯得不以為意,依舊每天開着一輛紅色全新鋒范靠自己的努力打拚一番事業。其實除掉富二代這個身份后,大毛和現在眾多打拚的年輕人並沒有區別,相比華而不實的超跑,一輛十萬級別,經濟適用的轎車反而才是事業上最大的助力。

與全新鋒范1.5L地球夢科技發動機相匹配的是支持自動啟停功能的CVT無級變速箱,平順、有力的換擋過程為駕駛者帶來暢快的加速體驗以及低至5.4L的百公里油耗表現。如此經濟的表現對於年輕人來說更是大大減輕了用車壓力。並且全新鋒范搭載了時下先進的科技配置,中控7英寸多功能互聯显示系統使車型氛圍充滿了未來感,除了可與多種主流手機型號連接並同步操作以外,屏幕內還集成了先進的三模式倒車影像,為駕駛者提供了一個清晰、直觀的後方視角,使車輛在倒車時更有安全保障。全新鋒范強大的功能配置以及突出的性價比,堪稱生活好幫手、創業好夥伴,難怪能成為了大毛的選擇。

綜合來看,決意東山再起的時光與憑藉自身努力創業的大毛選擇全新鋒范作為座駕再適合不過,外觀內飾極具現代感,高科技配置豐富,低油耗高動力,一切都恰好滿足年輕人的需求。並且不久前的廣州車展上,廣汽本田又推出了2017款鋒范1.5L CVT精英版,新增多達7項配置,使得外觀造型與實用性上都得到了更好的提升。在外觀上加入了15寸鋁質輪轂與後置短天線。在實用性方面加入了對於如今年輕人來說不可或缺的無鑰匙進入/一鍵啟動功能,靠近車門自動解鎖,出發前的系列動作一氣呵成,說不出的瀟洒與帥氣。而後排出風口的出現,在讓所有坐上你車的人都能得到享受的同時,更是使得整輛車達到了越級的表現。

2017款鋒范1.5L CVT精英版的官方指導價為9.58萬元。如此豐富的配置,售價卻未超出10萬,再加上即將結束的購置稅減半政策,性價比在同級車型中非常突出,可見廣汽本田的十足誠意。所以十萬級合資轎車中,2017款鋒范不失為一個上上之選。

追劇的同時不忘給大家推薦車型,小編感覺自己今天一米八!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

這台車日行1200公里 連外國人都驚訝

上汽大通的海外市場表現澳新市場作為上汽大通的第一大海外市場,而且也是唯一獲得大批量歐洲政府採購、唯一進入歐洲國家政府執法機構的中國品牌,同時,在澳洲也成為了銷量第一的中國汽車品牌,在1-9月,上汽大通在澳新市場的銷量達到1791台,同比去年銷量增長了90。

一份值得學習的敬業

戴夫已經在陶朗加(新西蘭北島北部港市)生活了40多年,而對於工作,他說是一種享受,還必不想退休,每天工作的開始先從家中出發,北上漢密爾頓裝貨、再到北邊的奧克蘭進行卸貨和裝報,然後返回陶朗加換乘後半夜的司機繼續南下吉斯伯恩完成《先驅報》的送遞並返回家中。

戴夫從1986年開始從事送遞業務,一直從事報紙信件投遞的工作,30年送遞工作已經累計送達4000萬份貨物。

6年前,戴夫就為新西蘭的第一大報社《先驅報》(日報,日發行量15萬份)進行送貨,這條線路一年下來需要跑45萬公里,從第一次駕駛上汽大通的V80已經有2年多了,不到3年的時間里,前兩周破一百萬公里總行駛里程(三台V80里程合計)。

目前一共有5個司機,實行輪班制,負責陶朗加到漢密爾頓、奧克蘭再南下吉斯伯恩,並最後返回陶朗加約1200公里里程的線路,除了聖誕和復活節,工作風雨無阻,實行做三天休息三天的交替輪班制度,對於已經到了退休年齡的戴夫,還沒有考慮退休必很享受這份工作,會一直開下去。

上汽大通的海外市場表現

澳新市場作為上汽大通的第一大海外市場,而且也是唯一獲得大批量歐洲政府採購、唯一進入歐洲國家政府執法機構的中國品牌,同時,在澳洲也成為了銷量第一的中國汽車品牌,在1-9月,上汽大通在澳新市場的銷量達到1791台,同比去年銷量增長了90.9%,作為上汽大通的第一大海外市場,澳新市場的銷量佔據了上汽大通整個海外銷量的30%。

上汽在澳洲市場的大熱和良好的聲譽,其最根本的原因還是自身產品的性價比高,更合理的市場價,更合理的豐富的配置及良好的安全性和耐用性,都是讓它在市場立足必越來越強的原因,雖然大通進入澳州市場不久,但勢頭穩步上升,可見上汽集團的全球化戰略正初見成效,接下來,上汽大通還將向澳新市場導入一系列新品。包括純電動寬體輕客EV80、中高端皮卡、中大型SUV D90等,隨着新品投放,期待上汽大通如何將代表中國汽車,成為歐美日韓車系之外的又一股強大力量,不僅使中國汽車在澳新市場崛起,並且希望為澳新消費者帶來更多更優秀的產品和服務。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

Koa源碼解析,帶你實現一個迷你版的Koa

前言

本文是我在閱讀 Koa 源碼后,並實現迷你版 Koa 的過程。如果你使用過 Koa 但不知道內部的原理,我想這篇文章應該能夠幫助到你,實現一個迷你版的 Koa 不會很難。

本文會循序漸進的解析內部原理,包括:

  1. 基礎版本的 koa
  2. context 的實現
  3. 中間件原理及實現

文件結構

  • application.js: 入口文件,裡面包括我們常用的 use 方法、listen 方法以及對 ctx.body 做輸出處理
  • context.js: 主要是做屬性和方法的代理,讓用戶能夠更簡便的訪問到request和response的屬性和方法
  • request.js: 對原生的 req 屬性做處理,擴展更多可用的屬性和方法,比如:query 屬性、get 方法
  • response.js: 對原生的 res 屬性做處理,擴展更多可用的屬性和方法,比如:status 屬性、set 方法

基礎版本

用法:

const Coa = require('./coa/application')
const app = new Coa()

// 應用中間件
app.use((ctx) => {
  ctx.body = '<h1>Hello</h1>'
})

app.listen(3000, '127.0.0.1')

application.js:

const http = require('http')

module.exports = class Coa {
  use(fn) {
    this.fn = fn
  }
  // listen 只是語法糖  本身還是使用 http.createServer
  listen(...args) {
    const server = http.createServer(this.callback())
    server.listen(...args)
  }
  callback() {
    const handleRequest = (req, res) => {
      // 創建上下文
      const ctx = this.createContext(req, res)
      // 調用中間件
      this.fn(ctx)
      // 輸出內容
      res.end(ctx.body)
    }
    return handleRequest
  }
  createContext(req, res) {
    let ctx = {}
    ctx.req = req
    ctx.res = res
    return ctx
  }
}

基礎版本的實現很簡單,調用 use 將函數存儲起來,在啟動服務器時再執行這個函數,並輸出 ctx.body 的內容。

但是這樣是沒有靈魂的。接下來,實現 context 和中間件原理,Koa 才算完整。

Context

ctx 為我們擴展了很多好用的屬性和方法,比如 ctx.query、ctx.set()。但它們並不是 context 封裝的,而是在訪問 ctx 上的屬性時,它內部通過屬性劫持將 request 和 response 內封裝的屬性返回。就像你訪問 ctx.query,實際上訪問的是 ctx.request.query。

說到劫持你可能會想到 Object.defineProperty,在 Kao 內部使用的是 ES6 提供的對象的 setter 和 getter,效果也是一樣的。所以要實現 ctx,我們首先要實現 request 和 response。

在此之前,需要修改下 createContext 方法:

// 這三個都是對象
const context = require('./context')
const request = require('./request')
const response = require('./response')

module.exports = class Coa {
  constructor() {
    this.context = context
    this.request = request
    this.response = response
  }
  createContext(req, res) {
    const ctx = Object.create(this.context)
    // 將擴展的 request、response 掛載到 ctx 上
    // 使用 Object.create 創建以傳入參數為原型的對象,避免添加屬性時因為衝突影響到原對象
    const request = ctx.request = Object.create(this.request)
    const response = ctx.response = Object.create(this.response)
    
    ctx.app = request.app = response.app = this;
    // 掛載原生屬性
    ctx.req = request.req = response.req = req
    ctx.res = request.res = response.res = res
    
    request.ctx = response.ctx = ctx;
    request.response = response;
    response.request = request;
    
    return ctx
  }
}

上面一堆花里胡哨的賦值,是為了能通過多種途徑獲取屬性。比如獲取 query 屬性,可以有 ctx.query、ctx.request.query、ctx.app.query 等等的方式。

如果你覺得看起來有點冗餘,也可以主要理解這幾行,因為我們實現源碼時也就用到下面這些:

const request = ctx.request = Object.create(this.request)
const response = ctx.response = Object.create(this.response)

ctx.req = request.req = response.req = req
ctx.res = request.res = response.res = res

request

request.js:

const url = require('url')

module.exports = {
 /* 查看這兩步操作
  * const request = ctx.request = Object.create(this.request)
  * ctx.req = request.req = response.req = req 
  * 
  * 此時的 this 是指向 ctx,所以這裏的 this.req 訪問的是原生屬性 req
  * 同樣,也可以通過 this.request.req 來訪問
  */
  // 請求的 query 參數
  get query() {
    return url.parse(this.req.url).query
  },
  // 請求的路徑
  get path() {
    return url.parse(this.req.url).pathname
  },
  // 請求的方法
  get method() {
    return this.req.method.toLowerCase()
  }
}

response

response.js:

module.exports = {
  // 這裏的 this.res 也和上面同理 
  // 返回的狀態碼
  get status() {
    return this.res.statusCode
  },
  set status(val) {
    return this.res.statusCode = val
  },
  // 返回的輸出內容
  get body() {
    return this._body
  },
  set body(val) {
    return this._body = val
  },
  // 設置頭部
  set(filed, val) {
    if (typeof filed === 'string') {
      this.res.setHeader(filed, val)
    }
    if (toString.call(filed) === '[object Object]') {
      for (const key in filed) {
        this.set(key, filed[key])
      }
    }
  }
}

屬性代理

通過上面的實現,我們可以使用 ctx.request.query 來訪問到擴展的屬性。但是在實際應用中,更常用的是 ctx.query。不過 query 是在 request 的屬性,通過 ctx.query 是無法訪問的。

這時只需稍微做個代理,在訪問 ctx.query 時,將 ctx.request.query 返回就可以實現上面的效果。

context.js:

module.exports = {
    get query() {
        return this.request.query
    }
}

實際的代碼中會有很多擴展的屬性,總不可能一個一個去寫吧。為了優雅的代理屬性,Koa 使用 delegates 包實現。這裏我就直接簡單封裝下代理函數,代理函數主要用到__defineGetter__ 和 __defineSetter__ 兩個方法。

在對象上都會帶有 __defineGetter__ 和 __defineSetter__,它們可以將一個函數綁定在當前對象的指定屬性上,當屬性被獲取或賦值時,綁定的函數就會被調用。就像這樣:

let obj = {}
let obj1 = {
    name: 'JoJo'
}
obj.__defineGetter__('name', function(){
    return obj1.name
})

此時訪問 obj.name,獲取到的是 obj1.name 的值。

了解這個兩個方法的用處后,接下來開始修改 context.js:

const proto = module.exports = {
}

// getter代理
function delegateGetter(prop, name){
  proto.__defineGetter__(name, function(){
    return this[prop][name]
  })
}
// setter代理
function delegateSetter(prop, name){
  proto.__defineSetter__(name, function(val){
    return this[prop][name] = val
  })
}
// 方法代理
function delegateMethod(prop, name){
  proto[name] = function() {
    return this[prop][name].apply(this[prop], arguments)
  }
}

delegateGetter('request', 'query')
delegateGetter('request', 'path')
delegateGetter('request', 'method')

delegateGetter('response', 'status')
delegateSetter('response', 'status')
delegateGetter('response', 'body')
delegateSetter('response', 'body')
delegateMethod('response', 'set')

中間件原理

中間件思想是 Koa 最精髓的地方,為擴展功能提供很大的幫助。這也是它雖然小,卻很強大的原因。還有一個優點,中間件使功能模塊的職責更加分明,一個功能就是一个中間件,多个中間件組合起來成為一個完整的應用。

下面是著名的“洋蔥模型”。這幅圖很形象的表達了中間件思想的作用,它就像一個流水線一樣,上游加工后的東西傳遞給下游,下游可以繼續接着加工,最終輸出加工結果。

原理分析

在調用 use 註冊中間件的時候,內部會將每个中間件存儲到數組中,執行中間件時,為其提供 next 參數。調用 next 即執行下一个中間件,以此類推。當數組中的中間件執行完畢后,再原路返回。就像這樣:

app.use((ctx, next) => {
  console.log('1 start')
  next()
  console.log('1 end')
})

app.use((ctx, next) => {
  console.log('2 start')
  next()
  console.log('2 end')
})

app.use((ctx, next) => {
  console.log('3 start')
  next()
  console.log('3 end')
})

輸出結果如下:

1 start
2 start
3 start
3 end
2 end
1 end

有點數據結構知識的同學,很快就想到這是一個“棧”結構,執行的順序符合“先入后出”。

下面我將內部中間件實現原理進行簡化,模擬中間件執行:

function next1() {
  console.log('1 start')
  next2()
  console.log('1 end')
}
function next2() {
  console.log('2 start')
  next3()
  console.log('2 end')
}
function next3() {
  console.log('3 start')
  console.log('3 end')
}
next1()

執行過程:

  1. 調用 next1,將其入棧執行,輸出 1 start
  2. 遇到 next2 函數,將其入棧執行,輸出 2 start
  3. 遇到 next3 函數,將其入棧執行,輸出 3 start
  4. 輸出 3 end,函數執行完畢,next3 彈出棧
  5. 輸出 2 end,函數執行完畢,next2 彈出棧
  6. 輸出 1 end,函數執行完畢,next1 彈出棧
  7. 棧空,全部執行完畢

相信通過這個簡單的例子,都大概明白中間件的執行過程了吧。

原理實現

中間件原理實現的關鍵點主要是 ctx 和 next 的傳遞。

function compose(middleware) {
  return function(ctx) {
    return dispatch(0)
    function dispatch(i){
      // 取出中間件
      let fn = middleware[i]
      if (!fn) {
        return
      }
      // dispatch.bind(null, i + 1) 為應用中間件接受到的 next
      // next 即下一個應用中間件
      fn(ctx, dispatch.bind(null, i + 1))
    }
  }
}

可以看到,實現過程本質是函數的遞歸調用。在內部實現時,其實 next 沒有做什麼神奇的操作,它就是下一个中間件調用的函數,作為參數傳入供使用者調用。

下面我們來單獨測試 compose,你可以將它粘貼到控制台上運行:

function next1(ctx, next) {
  console.log('1 start')
  next()
  console.log('1 end')
}
function next2(ctx, next) {
  console.log('2 start')
  next()
  console.log('2 end')
}
function next3(ctx, next) {
  console.log('3 start')
  next()
  console.log('3 end')
}

let ctx = {}
let fn = compose([next1, next2, next3])
fn(ctx)

最後,因為 Koa 中間件是可以使用 async/await 異步執行的,所以還需要修改下 compose 返回 Promise:

function compose(middleware) {
  return function(ctx) {
    return dispatch(0)
    function dispatch(i){
      // 取出中間件
      let fn = middleware[i]
      if (!fn) {
        return Promise.resolve()
      }
      // dispatch.bind(null, i + 1) 為應用中間件接受到的 next
      // next 即下一個應用中間件
      try {
        return Promise.resolve( fn(ctx, dispatch.bind(null, i + 1)) )
      } catch (error) {
        return Promise.reject(error)
      }
    }
  }
}

應用

實現完成中間件的邏輯后,將它應用到迷你版Koa中,原來的代碼邏輯要做一些修改(部分代碼忽略)

application.js:

module.exports = class Coa {
  constructor() {
    // 存儲中間件的數組 
    this.middleware = []
  }

  use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    // 將中間件加入數組
    this.middleware.push(fn)
    return this
  }
  
  listen(...args) {
    const server = http.createServer(this.callback())
    server.listen(...args)
  }

  callback() {
    const handleRequest = (req, res) => {
      // 創建上下文
      const ctx = this.createContext(req, res)
      // fn 為第一個應用中間件
      const fn = this.compose(this.middleware)
      // 在所有中間件執行完畢后 respond 函數用於處理 ctx.body 輸出
      return fn(ctx).then(() => respond(ctx)).catch(console.error)
    }
    return handleRequest
  }
  
  compose(middleware) {
    return function(ctx) {
      return dispatch(0)
      function dispatch(i){
        let fn = middleware[i]
        if (!fn) {
          return Promise.resolve()
        }
        // dispatch.bind(null, i + 1) 為應用中間件接受到的 next
        try {
          return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
        } catch (error) {
          return Promise.reject(error)
        }
      }
    }
  }
}

function respond(ctx) {
  let res = ctx.res
  let body = ctx.body
  if (typeof body === 'string') {
    return res.end(body)
  }
  if (typeof body === 'object') {
    return res.end(JSON.stringify(body))
  }
}

完整實現

application.js:

const http = require('http')
const context = require('./context')
const request = require('./request')
const response = require('./response')

module.exports = class Coa {
  constructor() {
    this.middleware = []
    this.context = context
    this.request = request
    this.response = response
  }

  use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    this.middleware.push(fn)
    return this
  }

  listen(...args) {
    const server = http.createServer(this.callback())
    server.listen(...args)
  }

  callback() {
    const handleRequest = (req, res) => {
      // 創建上下文
      const ctx = this.createContext(req, res)
      // fn 為第一個應用中間件
      const fn = this.compose(this.middleware)
      return fn(ctx).then(() => respond(ctx)).catch(console.error)
    }
    return handleRequest
  }

  // 創建上下文
  createContext(req, res) {
    const ctx = Object.create(this.context)
    // 處理過的屬性
    const request = ctx.request = Object.create(this.request)
    const response = ctx.response = Object.create(this.response)
    // 原生屬性
    ctx.app = request.app = response.app = this;
    ctx.req = request.req = response.req = req
    ctx.res = request.res = response.res = res

    request.ctx = response.ctx = ctx;
    request.response = response;
    response.request = request;

    return ctx
  }

  // 中間件處理邏輯實現
  compose(middleware) {
    return function(ctx) {
      return dispatch(0)
      function dispatch(i){
        let fn = middleware[i]
        if (!fn) {
          return Promise.resolve()
        }
        // dispatch.bind(null, i + 1) 為應用中間件接受到的 next
        // next 即下一個應用中間件
        try {
          return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
        } catch (error) {
          return Promise.reject(error)
        }
      }
    }
  }
}

// 處理 body 不同類型輸出
function respond(ctx) {
  let res = ctx.res
  let body = ctx.body
  if (typeof body === 'string') {
    return res.end(body)
  }
  if (typeof body === 'object') {
    return res.end(JSON.stringify(body))
  }
}

寫在最後

本文的簡單實現了 Koa 主要的功能。有興趣最好還是自己去看源碼,實現自己的迷你版 Koa。其實 Koa 的源碼不算多,總共4個文件,全部代碼包括註釋也就 1800 行左右。而且邏輯不會很難,很推薦閱讀,尤其適合源碼入門級別的同學觀看。

最後附上完整實現的代碼:github

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準