可能對于一些人來說,流利說是一份工作,而對于我來說,流利說卻是一段深刻的旅程,改變了我的生活,也塑造了我的性情、人格。從 2013 年作為第 7 號員工加入流利說,為之效力6年,從最早期民房創業,到納斯達克 IPO,這段經歷,我自認為頗具一個理想主義者的傳奇色彩,有些故事,是我愿意,也值得分享的。
老王(流利說 CEO 王翌)以前總借他恩師的話說,人這一輩子能做一兩件漂亮事就不錯了。我覺得,流利說,算我過去做的一件漂亮事。以前,我也常和同事說,如果你現在一切的經歷,在日后不足以用故事說與人聽,可能你現在經歷的還不夠痛,你還不夠刻苦。所幸,過去的經歷,給我留下了幾個故事。
這一次,我想分享當初選擇加入流利說的故事,分享這個過程中我的思考、行動。一方面,單純的想記錄這段故事。因為隨著年歲增加,記性卻是退減的。文字是最好的保存記憶的方式;另一方面,常常遇到設計師朋友們聊如何選擇工作機會,遇到創業的邀約機會怎么判斷、決策的問題,我希望這個故事,能給遇到此類問題的朋友一些啟發。
好,聽故事吧。
2012 年,我退出了聯合創立一年半的公司。而在一年半以前,我從阿里云公司離開,放棄了一筆可觀的 RSU 股票,當時,我是阿里云最早的 28 位設計師之一。結束創業后,2012 年 11 月 14 日 19:17,我發了一條微博,表示想看看新的機會。這條微博,有 16 個轉發。其中有 1 個轉發,被流利說聯合創始人 Ben 看到,他把這條微博轉發給了聯合創始人王翌(流利說 CEO)。
在此之前,我和 Ben、王翌素不相識,網友都談不上。Ben 之所以看到那條轉發,是因為他關注好友里面,有一位正是我一款瀏覽器插件產品 – 微博急簡 的用戶。
△ 使用「微博急簡」前后,微博主頁的對比
容我多說幾句,介紹一下微博急簡這款產品。2011 年左右,新浪微博的使用體驗,非常糟糕,逐步商業化帶來的各種廣告,讓原本不好的體驗,變得更加讓我無法容忍。很快,一個叫 stylish 的 Chrome 插件工具,在微博設計/技術圈子里流傳。用戶安裝 stylish 插件后,通過修改 CSS 來定制自己的微博體驗。同時,你還可以把 CSS 分享給其他用戶。
這太好玩了!我按自己的使用喜好,給自己訂制了一套極簡體驗的新浪微博。玩了幾天后,我決定做一個改善微博體驗的瀏覽器插件。為什么要重新做一個插件呢?我認為,stylish 的使用門檻、操作成本都太高,僅僅是專業人士的玩具。我希望普通人,也可以通過一鍵安裝瀏覽器插件,獲得舒服的微博體驗。因此,我給插件取名 – 微博急簡??吹贸鰜?,我是多么急迫的想簡化微博的體驗。
兩周后,微博急簡就上線了。幾個版本后,體驗就趨于穩定。高峰的時候,有 10 萬左右的用戶使用,相關微博話題有 600 萬。有很多行業內的大咖成為我的用戶,像馮大輝、少楠、方軍等。獲得了很多用戶的好評,最讓我嘚瑟的是現任丁香園產品總監少楠的評價:用一個插件秒殺了新浪UED團隊。
通過微博急簡這個產品,我想分享以下幾點:
這三點,既是我的觀點,也是我的特質。正是因為這樣的特質,才遇到后面加入流利說的契機。
回到故事主線。
我發完那條微博后的 1 小時,王翌就給了我微博私信。老實說,今天是我第一次注意到這個「相隔 1 小時」的時間細節。王翌的行動力、執行力,對于人才的執著追求,著實讓我佩服。這不僅僅是對我,對流利說早期的員工,以及后面的核心員工,都是如此。
他的消息里,有三個關鍵詞:exciting、團隊成立 2 個月、移動教育。對于這條消息,我禮貌的回應,但內心其實是「呵呵」的。即使,我看到他 LinkedIn 主頁上 Google PM 的經歷,很亮眼。呵呵的原因是,這個公司太早期了,才兩個月。2013 年,移動教育是啥東西?而且,創始人還這么不務實,動不動就標榜是一個 exciting 的機會。
雖然,王翌后續一直聯系我,但我基本是忽略的狀態。期間,我短暫的加入了一個朋友的創業公司。2013 年 1 月底,春節前的幾天,我再次收到王翌的私信:流利說 App 即將上架,想約我再聊一聊。于是,我們約在文二西路白鴉(有贊創始人)的貝塔咖啡。
?
△ 貝塔咖啡館
那天晚上,王翌給我展示了流利說最早的 App,程序其實還不太穩定。但他仍舊極具信心的表達了對于語音互動的看好,以及發出盛情邀請。我對這個 App 的產品與交互的第一印象,是好的,但對于王翌的第一印象卻是復雜的,既覺得這人有激情、有想法,同時又覺得不太靠得住,夸夸其談,他太會說了。
深色模式該從何處著手設計又要考慮哪些因素?一起來看看~
其實回顧我們常用的APP,有很多都更新了深色模式,而且每個APP對深色的定義和設計都有差異。
實際上深色模式已經來臨,而且在很多產品中都能發現它的身影,之后也會愈加流行。那么設計師面對深色模式,該從何處著手設計又要考慮哪些因素呢?
本文就為大家提供一份全面的總結。文章目錄如下:
1. 需求趨勢
過去一年以來,Android 10和iOS 13上都適配了深色模式,而且Apple和Google也一直致力于將資源和注意力投入到深色模式中,這也讓深色模式備受用戶的關注。
2. 專注內容
深色模式在弱光環境下具有更好的可讀性,讓我們更專注于眼前的屏幕。同時深色的背景會降低內容周圍元素的影響,特別是以圖片和視頻為主的應用,讓用戶更專注于內容。
作為內容消費型應用的Netflix ,把深色背景作為默認設計樣式,深色的設計讓用戶更能集中注意力,延長使用時間。
3. 減輕刺激
相對于其他顏色,深色系的設計在夜晚看著最舒服??赡芡砩贤媸謾C不用擔心光線太刺眼,但是深色模式對護眼并沒有什么幫助,只能說可以減少對眼睛的刺激。
4. 提高續航
深色模式更省電只適用于OLED屏幕。OLED面板的每個像素點可以單獨發光,當使用深色模式時,部分像素點被熄滅,只點亮部分像素,屏幕的一部分相當于處在休眠狀態,所以會更加省電。
在深色模式下,Apple重新審視了iOS中UI樣式和顏色的含義,讓我們來看看在iOS上設計深色模式帶來的變化。
語義化顏色(Semantic Colors)
所謂語義化顏色,就是不再通過某一固定的RGB色值來描述顏色,而是根據用途來描述,讓界面元素可以自動適配當前的外觀模式。
淘寶團隊就參考了蘋果官方的適配建議,通過語義化顏色的方式進行適配,使適配成本大幅降低。設計師根據不同UI元素的特性先期制定顏色語義化規則,進而技術在框架層面通過「顏色自動反轉」技術實現顏色反轉。
系統顏色
除了語義化顏色,Apple還提供了9種預定義的系統顏色,在淺色和深色模式中,這些顏色會動態變化,支持整個系統的外觀,同樣也可以自適應選定的界面樣式。
模糊與動態效果
在iOS13上,蘋果引入了4種模糊效果和8種動態效果,它們自動適應iOS界面樣式。這是在淺色和深色模式下不同的模糊效果。
蘋果還在iOS深色模式排版套件中引入4種動態效果,其中3種為疊加效果,1種分隔效果。
谷歌提供了廣泛的文檔支持,幫助設計師了解深色主題如何在Android生態系統中運行。
Elevation(陰影)
UI界面元素間的投影最能讓用戶清晰地感知用戶界面的深度。在設計深色主題時,組件將保留與淺色主題相同的默認陰影組件。Elevation越靠上, 顏色就會越淺。
無障礙性與對比
深色UI設計中的背景應足夠暗以顯示白色文本。設計師要注意背景和文字之間至少使用15.8:1的對比度。這樣可以確保將正文放在最前面時,能通過WCAG(Web內容無障礙指南,使網站內容更容易訪問)的AA標準。
顏色
深色模式必須避免飽和的顏色,以免引起眼睛疲勞。相反,設計師應專注于使用不飽和的顏色,以增加清晰度。主色和輔色的選擇還取決于對淺色和深色UI主題的考慮。
文字不透明度
在深色背景上設計淺色文字時,高度強調的文字不透明度為87%;一般提示文字的不透明度為60%;禁用文字的不透明度為38%。
蘋果和谷歌都利用各自的設計原則,為深色模式設計做準備工作。在實際設計過程中,不單需要這些基本原則,更重要的是要注意設計深色模式的實用要點。
設計深色背景時不是簡單的把白變成黑,而是對背景使用比較暗的色調,以減少眼睛疲勞。
在淺色模式中,我們傾向于用細微的陰影來傳達界面深度,使用起來更加自然。但是在大多數深色模式下,陰影的效果并不明顯,通常用顏色的深淺來傳達界面的層級關系。
關鍵點:注意應用場景
在知乎的深色模式中,背景的設計從深到淺使用了三級灰度,讓頁面的層級更分明。
一級灰度的應用場景主要是大的背景色,使用面積相對比較大顏色最深;二級灰度的應用場景是選項的背景色,根據選項的數量設置使用面積,位置排布比較靈活;三級灰度的顏色最淺,使用面積最小,通常用在分割線中。
白底黑字和黑底白字帶給我們的用眼體驗是不一樣的。設計不當的深色模式常常因為強對比而變得很刺眼,同時為了提高對光線的吸收虹膜會張得更開,更容易造成眼部疲勞。
關鍵點:文字間的對比
深色模式中,文字的用色通常是純灰色,不同位置的文字例如標題、正文和注釋使用深淺不同的顏色作對比。上圖是深色的微信,就利用這種方法來區分不同文字內容,展示文字層次關系。
另外每個應用的定位都不一樣,界面中想傳達的信息也有差異,所以要注意不同的設計思路。
關鍵點:文字與背景的對比
已經更新深色模式的應用主要分為兩大類,一類屬于工具型應用例如QQ、微信、百度網盤等,這類應用追求的是信息的有效傳達,在設計時文字內容和背景色的區分比較明顯。
上圖是百度網盤的深色模式,可以看出來標題文字與背景有很明顯的對比,保障了用戶使用時的可操作性和易讀性。
這樣的設計不需要用戶過于沉浸式的閱讀,只需要幫助用戶快速找到有用的信息并方便使用,這是工具型應用在設計深色模式時必備的原則。
另一類屬于內容型應用例如知乎、簡書等,這些應用更注重沉浸式的閱讀體驗,因為用戶通常會在某個界面中停留很久來查看內容,所以需要文字與背景的低對比度為閱讀營造柔和的氛圍。
簡書的深色模式中,文字與背景的對比關系就設計得很弱,整個界面呈現出灰色調,這樣的設計有助于在弱光環境下的長時間閱讀和瀏覽。
深色模式應該避免使用特別鮮艷的顏色,較高的明度和飽和度會與深色背景形成強烈的對比,讓頁面的可讀性變差并加深刺激。
關鍵點:降低色彩明度
在由淺變深的過程中,知乎對改變了界面中所有圖標的顏色。界面里面的圖標和主題按鈕的色彩,在色相、飽和度上都沒有變化,但是明度被不同程度的降低,保證了在不同光照條件下的內容的可讀性。
這是深色模式中處理色彩的一種方法,雖然在淺色界面中,我們更喜歡鮮艷的顏色,但明度低的顏色更適合深色主題。匹配這兩個模式另一個比較好的方法是創建互補的色板。
無論深色或者淺色,都只是產品向用戶呈現的一種界面狀態,最終的目的是為了更良好的使用體驗。
不管選擇什么樣的模式,都要記得從產品自身出發,并牢記這幾點:
文章來源:優設 作者:Clip設計夾
藍藍設計 魏華寫
在承擔ui設計項目中,常常碰到一些客戶不給設計師看己往的軟件或本公司設計師設計但沒有被客戶認可的設計方案,怕限制設計師的思路。那究竟給設計師參考會不會限制設計師的思維呢?
實際上不會的,這是更多維度了解客戶的思考和角度,排除己被嘗試過的選項。
在進行一個ui 設計前,深度方面應該要了解業務,用戶角色,環境生態,交互流程,關鍵功能,核心價值,用戶體驗的峰值點和難點在哪里…….總之了解的越深越好。
寬度方面,要看到競品分析,行業趨勢,國內外優秀案例欣賞,專業文章觀點,應該是可以拓展設計師的思維。
設計最核心的目標應該在于解決問題,而不是單純的讓界面好看。
優秀的設計師要明確解決問題的目標,博采眾長,獨立思考,看眾多的競品是看眾多的解題思路,多方位角度看問題。各產品資源,核心技術不同,取舍不同,理解信息架框,運營思路,用戶特征,技術實現可能性,不會只照著您給的資料比貓畫虎。
因此,我認為,放心的給設計師參考資料吧,互相的了解,溝通越多,越容易出好的作品。
這五張圖是最近藍藍設計的稿件,展現從一個模糊的概念性需求到一個可視化概念性方案的設計過程。
關于循環設計,可持續發展是商業領域非常關注的話題,作為UX需提前轉變思維,給企業帶來更多價值,一線大廠已在運用這種思維
本文共 3589 字,預計閱讀 10 分鐘
譯者推薦|本文從“可持續”和“設計”的兩點談起,來論述從線性經濟向可持續經濟的轉變,以及“可持續設計”的四個主要階段:理解、定義、制造、發布。
“循環設計”不是為了追求時髦或者抬升設計地位,而是將這個已經日益庸俗化的“設計”冠為自己的定語,是設計本身發展所趨,以及可持續發展所需,設計界需要對自己的責任有所承擔,形成一個全局觀、系統性看待設計問題的方式。讓回收利用和可持續發展成為一種規范,從而做到一勞永逸。
我們生活在一個呼喚變革的世界。在過去的50年中,現代社會所依賴的漫不經心和無休止的消費是不可持續的。我們從小就不關心自己的事情。如果有什么東西壞了,我們也就不修了。產品被設計成用完直接丟棄,而不是去修復。數字產品也不例外。然而,為了解決這些問題,出現了一種新的思維方式:循環設計(可持續設計)①。(益達說:其實這個理念和風格已經存在了很長的時間,大多應用在不為大眾所知的能源、材料再生流程之中,然而隨著時代的發展,循環設計可以變得更加普世。)
①注:循環設計是20世紀80-90年代產生的一種設計風格,他又稱回收設計,是指實現廣義收回和利用的方法,即在進行產品設計時,充分考慮產品零部件及材料回收的可能性,回收價值的大致方法,回收處理結構工藝性等于與回收有關的一系列問題,以達到零部件及材料資源和能源的再利用。它旨在通過設計來節約能源和材料,減少對環境的污染,使人類的設計物能多次反復利用,形成產品設計和使用的良性循環。
那么,循環設計方法意味著什么?在數字產品上要如何使用?在回答這些問題之前,首先,我們需要仔細觀察我們是如何構建我們的世界,為什么這個世界已經不可持續了,并且要理解環保世界的需求是如何改變我們的思維方式,促使我們渴望從線性設計模型轉變為循環設計模型。
向循環轉變
我們的經濟主要基于“按需配置”流程之上。在此線性系統中,我們構建了會在一段時間后淘汰的產品,并且將其組件視為垃圾。與此相反,循環設計方法將產品的生命周期視為一個閉環,其中資源不斷地被重新利用。
在“經典”線性模型中,產品經歷了生產、消費和破壞的各個階段,最終以浪費告終。在設計一款循環產品過程中,我們使用的方法包含四大階段,這四個階段形成了一個閉環,并形成了一個恒定的循環,在這個循環中,不僅僅只有閃閃發亮的、新的,未使用過的材料才被受歡迎。
循環設計方法的四個階段是:
理解 / 定義 / 制造 / 發布
當我們同時看線性設計和循環設計模型方法時,有一點吸引人的是,開始設計東西的時候,方法的差異。從只是生產某種東西,到對我們將要生產的產品做出深思熟慮的決定,以及在實施過程中付出的努力和關心,這是一個大轉變。
看看我們現在的立場
為什么做出這種轉變如此的重要?我確信每個看新聞的人都聽說過氣候變化。NASA 致力于解決環境問題,因此我們都可以非常詳細地了解人類行為和無限增長對我們生態系統的影響。
但好消息是我們不必繼續這樣做,因為我們可以很容易從數字世界中“產生”方式中學習事物的產生。電力廢棄物已成為現代世界的主要廢棄物來源之一。大量的手機和電腦被扔掉,隨之經濟是建立在每年都有新東西的基礎上的。
當您的手機屏幕意外碎裂時,我們該怎么辦?我們知道如何處理它嗎?我們知道如何修理嗎?我們并不知道……但是幸運的是,有些設計師對此問題提出了解決方案。Fairphone② 是一種合乎情理,模塊化的智能手機,其組件數量很少,可以輕松更換和回收。大公司也應朝這個方向邁出一步,讓回收利用和可持續發展成為一種時尚和規范,一勞永逸。
② Fairphone:這家公司生產的手機希望實現全球手機供應鏈的公平貿易,具體而言就是不使用“沖突礦物”并且確保生產手機的工人沒有被奴役和壓榨,目前仍然堅持在非洲貧困和戰亂的國家進口材料,已經在剛果和盧旺達境內找到了一些礦山,用更好的商業實踐推動當地經濟更健康地發展。
設計和設計師的重要性
設計師,比任何其他專業人士,都更有可能在一轉變中產生巨大的影響的人。我還敢說,我們有責任采用可持續設計的方式行動和思考。因為是我們創造了那些最終出現在傳送帶上的東西。我們也有責任教育我們的用戶。幸運的是,越來越多的人重視具有可持續發展目標的產品或品牌,或者重視起在產品背后有意義的故事。同樣,可持續發展不僅成為流行語,而且成為一種價值觀,被越來越多的人意識到基于有限資源的無限增長是無法實現的目標。但是,要從線性經濟向可持續經濟轉變,我們需要學習不同的思維方式。幸運的是,智能設備和數字產品的時代帶來了一種復雜的設計思維方法,可以作為物理世界中生產鏈的范例。
用戶體驗必須提供什么
地球上有一個地方,您不能隨便丟東西:互聯網。這是一個對已有產品進行再構思的地方,您只能去完善它,不能丟棄它,因為如果您一夜之間說:“我不喜歡我的網站,明天我將推出一個全新的網站”,那您便會失去用戶。
如果我們看一下可持續發展設計方法的四個主要階段,就會發現我們在用戶體驗設計中使用的方法與此很相似。
讓我們再次看一下四個階段,然后將其更詳細地分解:
當我們談論與循環設計相關的理解時,我們談論的是在開始設計一個未來的產品之前就了解它的用戶和環境。研究一直是數字產品設計的基礎。與數字產品的連接比與實體產品的連接要更多的涉及到人類的心理。因此不可避免地要開發出新的研究方法,以幫助我們洞察用戶在使用某種產品時的想法、感受和行為。但這不僅與用戶有關, 研究還必須深入到經濟領域,并研究未來產品的組成部分,同時牢記它們必須可被再次利用。
在此階段,將定義(商業)目標,并構建一個商業模型畫布作為生產過程的計劃。用戶體驗使用這種方法已有一段時間了,讓涉眾參與其中,并在設計過程中更多地激活它們。為我們設計的產品設定一個目標是至關重要的,因為有了它,我們可以為用戶創造額外的價值。因此,無論是制作商業模型畫布還是舉辦精彩的價值主張研討會,在生產方式中實施這些方法都會對當前的生產流程產生巨大的影響。
這是關鍵部分?,F在我們正在做的事情就好像沒有明天一樣。隨著每種無法回收的產品的出現,我們產生的廢料越來越多。但是循環方法是為產品創建一個原型,并定義將需要使用那些材料反映在產品原型上,并在定義階段概述的商業模型上定義材料。原型設計和構思是用戶體驗設計過程中的關鍵要素,這也是為什么需要制作原型。
根據循環設計模型,隨著產品的發布,生產周期進入了第四階段,然同時理解階段又重新開始了。對于數字產品來說,這是自然發生的事前:你發布一個產品,基于該版本收集反饋,然后構思它,周而復始,這個循環再次產生。
但是,觀察這個循環并建立這些連接僅僅是冰山一角。在數字時代發展起來的設計思維給世界帶來了許多反思。
變革中的大佬
幸運的是,已經有許多大品牌意識到轉變的必要性,并采取和提出了數字設計思維方法來支持轉變,并建立循環設計的時代。根據《循環設計指南》,“我們應該把我們設計的所有東西都看作軟件產品和服務——這些產品和服務可以基于我們通過反饋得到的數據而不斷的發?!?
用戶體驗研究和用戶體驗設計一直是在做的一件事是:基于全面的研究和真實的用戶需求來構建產品。上面的設計指南是非常復雜的工具,具有許多可能的方法。它強調了從產品到服務流程轉變的重要性,并展示如何使用敏捷流程并將其實施到構建產品的方法之中。
IDEO(全球頂尖的設計咨詢公司)與 Ellen Macarthur Foundation(艾倫·麥克阿瑟基金會)合作,試圖“試圖通過設計構建一個具有恢復性和再生性的經濟框架”。在這里,您可以找到幾乎每個生產方面和領域——例如食品、時裝、經濟和設計——并在每個領域中提出解決方案,以打破線性生產系統。
耐克還宣布了其基于循環設計模型生產高品質運動鞋的新方法原則。正如您已經看到的那樣,無論您身處哪個經濟領域,都可以為循環生產過程的蓬勃發展做貢獻,并成為一支主導力量。
重要的結論
我認為,作為設計師,我們始終要為變革而努力,并始終努力與客戶、產品或服務保持緊密的關系,并通過構思使其不斷完善,以實現這一目標。這是因為偉大的事情只有通過時間和不斷的反思才能實現。在離線世界中,數字設計過程也有很多東西可以貢獻。希望通過教育,能有更多的大公司意識到用戶真正想要的產品是具有更多功能并可持續使用的,而不僅僅是將它們當作一次性產品,一旦它們不像最初那樣光鮮就把她扔掉。
轉自:站酷-大猴兒er
網上可以找到前端開發社區貢獻的大量工具,這篇文章列出了我最喜歡的一些工具,這些工具給我的工作帶來了許多便利。
1. EnjoyCSS
老實說,雖然我做過許多前端開發,但我并不擅長 CSS。當我陷入困境時,EnjoyCSS 是我的大救星。EnjoyCSS 提供了一個簡單的交互界面,幫助我設計元素,然后自動輸出相應的 CSS 代碼。
EnjoyCSS 可以輸出 CSS、LESS、SCSS 代碼,并支持指定需要支持哪些瀏覽器及其版本。開發簡單頁面時用起來比較方便,但不太適合復雜一點的前端項目(這類項目往往需要引入 CSS 框架)。
2. Prettier Playground
Prettier 是一個代碼格式化工具,支持格式化 JavaScript 代碼(包括 ES2017、JSX、Angular、Vue、Flow、TypeScript 等)。Prettier 會移除代碼原本的樣式,替換為遵循最佳實踐的標準化、一致的樣式。IDE 大多支持 Prettier 工具,不過 Prettier 也有在線版本,讓你可以在瀏覽器里格式化代碼。
如果工作電腦不在手邊,使用移動端設備或者臨時借用別人的電腦查看代碼時,Prettier Playground 非常好用。相比在 IDE 或編輯器下使用 Prettier,個人更推薦通過 git pre-commit hook 配置 Prettier:hook 可以保證整個團隊使用統一的配置,免去各自分別配置 IDE 或編輯器的麻煩。如果是老項目,hook 還可以設置只格式化有改動的單個文件甚至有改動的代碼段,避免在 IDE 或編輯器下使用 Prettier 時不小心格式了大量代碼,淹沒了 commit 的主要改動,讓 review 代碼變得十分痛苦。
3. Postman
Postman 一直在我的開發工具箱里,測試后端 API 接口時非常好用。GET、POST、DELETE、OPTIONS、PUT 這些方法都支持。毫無疑問,你應該使用這個工具。
Postman 之外,Insomnia 也是很流行的 REST API 測試工具,亮點是支持 GraphQL。不過 Postman 從 去年夏天發布的 v7.2 起也支持了 GraphQL。
4. StackBlitz
Chidume Nnamdi 盛贊這是每個用戶最喜歡的在線 IDE。StackBlitz 將大家最喜歡、最常用的 IDE Visual Studio Code 搬進了瀏覽器。
StackBlitz 支持一鍵配置 Angular、React、Ionic、TypeScript、RxJS、Svelte 等 JavaScript 框架,也就是說,只需幾秒你就可以開始寫代碼了。
我覺得這個在線 IDE 很有用,特別是可以在線嘗試一些樣例代碼或者庫,否則僅僅嘗試一些新特性就需要花很多時間在新項目初始化配置上。有了 StackBlitz,無需在本地從頭搭建環境,花上幾分鐘就可以試用一個 NPM 包。很棒,不是嗎?
微軟官方其實也提供了在線版本的 VSCode,可以在瀏覽器內使用 VSCode,并且支持開發 Node.js 項目(基于 Azure)。不過 StackBlitz 更專注于優化前端開發體驗,界面更加直觀一點,也推出了 beta 版本的 Node.js 支持(基于 GCP,需要填表申請)。
5. Bit.dev
軟件開發的基本原則之一就是代碼復用。代碼復用減少了開發量,讓你不用從頭開發組件。
這正是 Bit.dev 做的事,分享可重用的組件和片段,降低開發量,加速開發進程。
除了公開分享,它還支持在團隊分享,讓團隊協作更方便。
正如 Bit.dev 的口號「組件即設計體系。協同開發更好的組件?!顾?,Bit.dev 可以用來創建設計體系,允許團隊內的開發者和設計師一起協作,從頭搭建一套設計體系。
Bit.dev 目前支持 React、Vue、Angular、Node 及其他 JavaScript 框架。
在 Bit.dev 上不僅可以搜索組件,還可以直接查看組件的依賴,瀏覽組件的代碼,甚至在線編輯代碼并查看預覽效果!選好組件后可以通過 Bit.dev 的命令行工具 bit 在本地項目引入組件,也可以通過 npm、yarn 引入組件。
6. CanIUse
CanIUse是非常好用的在線工具,可以方便地查看各大瀏覽器對某個特性的支持程度。
我過去經常碰到自己開發的應用的一些功能在其他瀏覽器下不支持的情況。比如我的作品集項目使用的某個特性在 Safari 下不支持,直到項目上線幾個月后我才意識到。這些經驗教訓讓我意識到需要檢查瀏覽器兼容性。
我們來看一個例子吧。哪些瀏覽器支持 WebP 圖像格式?
如你所見,Safari 和 IE 目前不支持 WebP。這意味著需要為不兼容的瀏覽器提供回退選項,比如:
<picture>
CanIUse 還可以在命令行下使用,例如,在命令行下查看 WebP 圖像格式的瀏覽器兼容性:caniuse webp(運行命令前需要事先通過 npm install -g caniuse-cmd安裝命令行工具。
方法參數的驗證
JavaScript 允許你設置參數的默認值。通過這種方法,可以通過一個巧妙的技巧來驗證你的方法參數。
const isRequired = () => { throw new Error('param is required'); };
const print = (num = isRequired()) => { console.log(`printing ${num}`) };
print(2);//printing 2
print()// error
print(null)//printing null
非常整潔,不是嗎?
格式化 json 代碼
你可能對 JSON.stringify 非常熟悉。但是你是否知道可以用 stringify 進行格式化輸出?實際上這很簡單。
stringify 方法需要三個輸入。 value,replacer 和 space。后兩個是可選參數。這就是為什么我們以前沒有注意過它們。要對 json 進行縮進,必須使用 space 參數。
console.log(JSON.stringify({name:"John",Age:23},null,'\t'));
>>>
{
"name": "John",
"Age": 23
}
從數組中獲取唯一值
要從數組中獲取唯一值,我們需要使用 filter 方法來過濾出重復值。但是有了新的 Set 對象,事情就變得非常順利和容易了。
let uniqueArray = [...new Set([1, 2, 3, 3, 3, "school", "school", 'ball', false, false, true, true])];
>>> [1, 2, 3, "school", "ball", false, true]
從數組中刪除虛值(Falsy Value)
在某些情況下,你可能想從數組中刪除虛值。虛值是 JavaScript 的 Boolean 上下文中被認定為為 false 的值。 JavaScript 中只有六個虛值,它們是:
undefined
null
NaN
0
"" (空字符串)
false
濾除這些虛值的最簡單方法是使用以下函數。
myArray.filter(Boolean);
如果要對數組進行一些修改,然后過濾新數組,可以嘗試這樣的操作。請記住,原始的 myArray 會保持不變。
myArray
.map(item => {
// Do your changes and return the new item
})
.filter(Boolean);
合并多個對象
假設我有幾個需要合并的對象,那么這是我的首選方法。
const user = {
name: 'John Ludwig',
gender: 'Male'
};
const college = {
primary: 'Mani Primary School',
secondary: 'Lass Secondary School'
};
const skills = {
programming: 'Extreme',
swimming: 'Average',
sleeping: 'Pro'
};
const summary = {...user, ...college, ...skills};
這三個點在 JavaScript 中也稱為展開運算符。你可以在這里學習更多用法。
對數字數組進行排序
JavaScript 數組有內置的 sort 方法。默認情況下 sort 方法把數組元素轉換為字符串,并對其進行字典排序。在對數字數組進行排序時,這有可能會導致一些問題。所以下面是解決這類問題的簡單解決方案。
[0,10,4,9,123,54,1].sort((a,b) => a-b);
>>> [0, 1, 4, 9, 10, 54, 123]
這里提供了一個將數字數組中的兩個元素與 sort 方法進行比較的函數。這個函數可幫助我們接收正確的輸出。
Disable Right Click
禁用右鍵
你可能想要阻止用戶在你的網頁上單擊鼠標右鍵。
<body oncontextmenu="return false">
<div></div>
</body>
這段簡單的代碼將為你的用戶禁用右鍵單擊。
使用別名進行解構
解構賦值語法是一種 JavaScript 表達式,可以將數組中的值或對象的值或屬性分配給變量。解構賦值能讓我們用更簡短的語法進行多個變量的賦值。
const object = { number: 10 };
// Grabbing number
const { number } = object;
// Grabbing number and renaming it as otherNumber
const { number: otherNumber } = object;
console.log(otherNumber); //10
獲取數組中的最后一項
可以通過對 splice 方法的參數傳入負整數,來數獲取組末尾的元素。
let array = [0, 1, 2, 3, 4, 5, 6, 7]
console.log(array.slice(-1));
>>>[7]
console.log(array.slice(-2));
>>>[6, 7]
console.log(array.slice(-3));
>>>[5, 6, 7]
等待 Promise 完成
在某些情況下,你可能會需要等待多個 promise 結束??梢杂?Promise.all 來并行運行我們的 promise。
const PromiseArray = [
Promise.resolve(100),
Promise.reject(null),
Promise.resolve("Data release"),
Promise.reject(new Error('Something went wrong'))];
Promise.all(PromiseArray)
.then(data => console.log('all resolved! here are the resolve values:', data))
.catch(err => console.log('got rejected! reason:', err))
關于 Promise.all 的主要注意事項是,當一個 Promise 拒絕時,該方法將引發錯誤。這意味著你的代碼不會等到你所有的 promise 都完成。
如果你想等到所有 promise 都完成后,無論它們被拒絕還是被解決,都可以使用 Promise.allSettled。此方法在 ES2020 的最終版本得到支持。
const PromiseArray = [
Promise.resolve(100),
Promise.reject(null),
Promise.resolve("Data release"),
Promise.reject(new Error('Something went wrong'))];
Promise.allSettled(PromiseArray).then(res =>{
console.log(res);
}).catch(err => console.log(err));
//[
//{status: "fulfilled", value: 100},
//{status: "rejected", reason: null},
//{status: "fulfilled", value: "Data release"},
//{status: "rejected", reason: Error: Something went wrong ...}
//]
即使某些 promise 被拒絕,Promise.allSettled 也會從你所有的 promise 中返回結果。
XML(Extensible Markup Language 可擴展標記語言),XML是一個以文本來描述數據的文檔。
<?xml version="1.0" encoding="UTF-8"?> <people> <person personid="E01"> <name>Tony</name> <address>10 Downing Street, London, UK</address> <tel>(061) 98765</tel> <fax>(061) 98765</fax> <email>tony@everywhere.com</email> </person> <person personid="E02"> <name>Bill</name> <address>White House, USA</address> <tel>(001) 6400 98765</tel> <fax>(001) 6400 98765</fax> <email>bill@everywhere.com</email> </person> </people>
(1)充當顯示數據(以XML充當顯示層)
(2)存儲數據(存儲層)的功能
(3)以XML描述數據,并在聯系服務器與系統的其余部分之間傳遞。(傳輸數據的一樣格式)
從某種角度講,XML是數據封裝和消息傳遞技術。
3.解析XML:// 創建SAX解析器工廠對象 SAXParserFactory spf = SAXParserFactory.newInstance(); // 使用解析器工廠創建解析器實例 SAXParser saxParser = spf.newSAXParser(); // 創建SAX解析器要使用的事件偵聽器對象 PersonHandler handler = new PersonHandler(); // 開始解析文件 saxParser.parse( new File(fileName), handler);
3.2. DOM解析XML:
DOM:Document Object Model(文檔對象模型)
DOM的特性:
定義一組 Java 接口,基于對象,與語言和平臺無關將 XML 文檔表示為樹,在內存中解析和存儲 XML 文檔,允許隨機訪問文檔的不同部分。
DOM解析XML
DOM的優點,由于樹在內存中是持久的,因此可以修改后更新。它還可以在任何時候在樹中上下導航,API使用起來也較簡單。
DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); DocumentBuilder db = builder.newDocumentBuilder(); db.parse("person.xml"); NodeList node_person = doc.getElementsByTagName("person");
3.3. JDOM解析XML:
JDOM是兩位著名的 Java 開發人員兼作者,Brett Mclaughlin 和 Jason Hunter 的創作成果, 2000 年初在類似于Apache協議的許可下,JDOM作為一個開放源代碼項目正式開始研發了。
JDOM 簡化了與 XML 的交互并且比使用 DOM 實現更快,JDOM 與 DOM 主要有兩方面不同。首先,JDOM 僅使用具體類而不使用接口。這在某些方面簡化了 API,但是也限制了靈活性。第二,API 大量使用了 Collections 類,簡化了那些已經熟悉這些類的 Java 開發者的使用。
解析步驟: (1)SAXBuilder sax = new SAXBuilder(); (2)Document doc = sax.build(….); (3)Element el = doc.getRootElement();(4)List list = el.getChildren(); (5)遍歷內容
解析步驟: (1)SAXReader sax = new SAXReader(); (2)Document doc = sax.read(Thread.currentThread().getContextClassLoader() .getResourceAsStream("person.xml")); (3)Element root = doc.getRootElement(); (4)Iterator iterator = root.elementIterator(); (5)遍歷迭代器
public class Person { private String personid; private String name; private String address; private String tel; private String fax; private String email; @Override public String toString() { return "Person{" + "personid='" + personid + '\'' + ", name='" + name + '\'' + ", address='" + address + '\'' + ", tel='" + tel + '\'' + ", fax='" + fax + '\'' + ", email='" + email + '\'' + '}'; } public String getPersonid() { return personid; } public void setPersonid(String personid) { this.personid = personid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } public String getFax() { return fax; } public void setFax(String fax) { this.fax = fax; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
<?xml version="1.0" encoding="UTF-8"?> <people> <person personid="E01"> <name>Tony Blair</name> <address>10 Downing Street, London, UK</address> <tel>(061) 98765</tel> <fax>(061) 98765</fax> <email>blair@everywhere.com</email> </person> <person personid="E02"> <name>Bill Clinton</name> <address>White House, USA</address> <tel>(001) 6400 98765</tel> <fax>(001) 6400 98765</fax> <email>bill@everywhere.com</email> </person> </people>
import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.util.ArrayList; import java.util.List; /** * Created by Hu Guanzhong * SAX解析的特點: * 1、基于事件驅動 * 2、順序讀取,速度快 * 3、不能任意讀取節點(靈活性差) * 4、解析時占用的內存小 * 5、SAX更適用于在性能要求更高的設備上使用(Android開發中) * */ public class PersonHandler extends DefaultHandler{ private List<Person> persons = null; private Person p;//當前正在解析的person private String tag;//用于記錄當前正在解析的標簽名 public List<Person> getPersons() { return persons; } //開始解析文檔時調用 @Override public void startDocument() throws SAXException { super.startDocument(); persons = new ArrayList<>(); System.out.println("開始解析文檔..."); } //在XML文檔解析結束時調用 @Override public void endDocument() throws SAXException { super.endDocument(); System.out.println("解析文檔結束."); } /** * 解析開始元素時調用 * @param uri 命名空間 * @param localName 不帶前綴的標簽名 * @param qName 帶前綴的標簽名 * @param attributes 當前標簽的屬性集合 * @throws SAXException */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); if ("person".equals(qName)){ p = new Person(); String personid = attributes.getValue("personid"); p.setPersonid(personid); } tag = qName; System.out.println("startElement--"+qName); } //解析結束元素時調用 @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); if ("person".equals(qName)) { persons.add(p); } tag = null; System.out.println("endElement--"+qName); } //解析文本內容時調用 @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); if (tag != null) { if ("name".equals(tag)) { p.setName(new String(ch,start,length)); }else if("address".equals(tag)){ p.setAddress(new String(ch,start,length)); }else if("tel".equals(tag)){ p.setTel(new String(ch,start,length)); }else if("fax".equals(tag)){ p.setFax(new String(ch,start,length)); }else if("email".equals(tag)){ p.setEmail(new String(ch,start,length)); } System.out.println(ch); } } }
public class XMLDemo { /** * 使用第三方xstream組件實現XML的解析與生成 */ @Test public void xStream(){ Person p = new Person(); p.setPersonid("1212"); p.setAddress("北京"); p.setEmail("vince@163.com"); p.setFax("6768789798"); p.setTel("13838389438"); p.setName("38"); XStream xStream = new XStream(new Xpp3Driver()); xStream.alias("person",Person.class); xStream.useAttributeFor(Person.class,"personid"); String xml = xStream.toXML(p); System.out.println(xml); //解析XML Person person = (Person)xStream.fromXML(xml); System.out.println(person); } /** * 從XML文件中讀取對象 */ @Test public void xmlDecoder() throws FileNotFoundException { BufferedInputStream in = new BufferedInputStream(new FileInputStream("test.xml")); XMLDecoder decoder = new XMLDecoder(in); Person p = (Person)decoder.readObject(); System.out.println(p); } /** * 把對象轉成XML文件寫入 */ @Test public void xmlEncoder() throws FileNotFoundException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.xml")); XMLEncoder xmlEncoder = new XMLEncoder(bos); Person p = new Person(); p.setPersonid("1212"); p.setAddress("北京"); p.setEmail("vince@163.com"); p.setFax("6768789798"); p.setTel("13838389438"); p.setName("38"); xmlEncoder.writeObject(p); xmlEncoder.close(); } /** * DOM4J解析XML * 基于樹型結構,第三方組件 * 解析速度快,效率更高,使用的JAVA中的迭代器實現數據讀取,在WEB框架中使用較多(Hibernate) * */ @Test public void dom4jParseXML() throws DocumentException { //1 創建DOM4J的解析器對象 SAXReader reader = new SAXReader(); InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/vince/xml/person.xml"); org.dom4j.Document doc = reader.read(is); org.dom4j.Element rootElement = doc.getRootElement(); Iterator<org.dom4j.Element> iterator = rootElement.elementIterator(); ArrayList<Person> persons = new ArrayList<>(); Person p = null; while(iterator.hasNext()){ p = new Person(); org.dom4j.Element e = iterator.next(); p.setPersonid(e.attributeValue("personid")); Iterator<org.dom4j.Element> iterator1 = e.elementIterator(); while(iterator1.hasNext()){ org.dom4j.Element next = iterator1.next(); String tag = next.getName(); if("name".equals(tag)){ p.setName(next.getText()); }else if("address".equals(tag)){ p.setAddress(next.getText()); }else if("tel".equals(tag)){ p.setTel(next.getText()); }else if("fax".equals(tag)){ p.setFax(next.getText()); }else if("email".equals(tag)){ p.setEmail(next.getText()); } } persons.add(p); } System.out.println("結果:"); System.out.println(Arrays.toString(persons.toArray())); } /** * JDOM解析 XML * 1、與DOM類似基于樹型結構, * 2、與DOM的區別: * (1)第三方開源的組件 * (2)實現使用JAVA的Collection接口 * (3)效率比DOM更快 */ @Test public void jdomParseXML() throws JDOMException, IOException { //創建JDOM解析器 SAXBuilder builder = new SAXBuilder(); InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/vince/xml/person.xml"); org.jdom2.Document build = builder.build(is); Element rootElement = build.getRootElement(); List<Person> list = new ArrayList<>(); Person person = null; List<Element> children = rootElement.getChildren(); for(Element element: children){ person = new Person(); String personid = element.getAttributeValue("personid"); person.setPersonid(personid); List<Element> children1 = element.getChildren(); for (Element e: children1){ String tag = e.getName(); if("name".equals(tag)){ person.setName(e.getText()); }else if("address".equals(tag)){ person.setAddress(e.getText()); }else if("tel".equals(tag)){ person.setTel(e.getText()); }else if("fax".equals(tag)){ person.setFax(e.getText()); }else if("email".equals(tag)){ person.setEmail(e.getText()); } } list.add(person); } System.out.println("結果:"); System.out.println(Arrays.toString(list.toArray())); } /** * DOM解析XML * 1、基于樹型結構,通過解析器一次性把文檔加載到內存中,所以會比較占用內存,可以隨機訪問 * 更加靈活,更適合在WEB開發中使用 */ @Test public void domParseXML() throws ParserConfigurationException, IOException, SAXException { //1、創建一個DOM解析器工廠對象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //2、通過工廠對象創建解析器對象 DocumentBuilder documentBuilder = factory.newDocumentBuilder(); //3、解析文檔 InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/vince/xml/person.xml"); //此代碼完成后,整個XML文檔已經被加載到內存中,以樹狀形式存儲 Document doc = documentBuilder.parse(is); //4、從內存中讀取數據 //獲取節點名稱為person的所有節點,返回節點集合 NodeList personNodeList = doc.getElementsByTagName("person"); ArrayList<Person> persons = new ArrayList<>(); Person p = null; //此循環會迭代兩次 for (int i=0;i<personNodeList.getLength();i++){ Node personNode = personNodeList.item(i); p = new Person(); //獲取節點的屬性值 String personid = personNode.getAttributes().getNamedItem("personid").getNodeValue(); p.setPersonid(personid); //獲取當前節點的所有子節點 NodeList childNodes = personNode.getChildNodes(); for (int j = 0;j<childNodes.getLength();j++){ Node item = childNodes.item(j); String nodeName = item.getNodeName(); if ("name".equals(nodeName)) { p.setName(item.getFirstChild().getNodeValue()); }else if("address".equals(nodeName)){ p.setAddress(item.getFirstChild().getNodeValue()); }else if("tel".equals(nodeName)){ p.setTel(item.getFirstChild().getNodeValue()); }else if("fax".equals(nodeName)){ p.setFax(item.getFirstChild().getNodeValue()); }else if("email".equals(nodeName)){ p.setEmail(item.getFirstChild().getNodeValue()); } } persons.add(p); } System.out.println("結果:"); System.out.println(Arrays.toString(persons.toArray())); } /** * SAX解析的特點: * 1、基于事件驅動 * 2、順序讀取,速度快 * 3、不能任意讀取節點(靈活性差) * 4、解析時占用的內存小 * 5、SAX更適用于在性能要求更高的設備上使用(Android開發中) * @throws ParserConfigurationException * @throws SAXException * @throws IOException */ @Test public void saxParseXML() throws ParserConfigurationException, SAXException, IOException { //1、創建一個SAX解析器工廠對象 SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); //2、通過工廠對象創建SAX解析器 SAXParser saxParser = saxParserFactory.newSAXParser(); //3、創建一個數據處理器(需要我們自己來編寫) PersonHandler personHandler = new PersonHandler(); //4、開始解析 InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/vince/xml/person.xml"); saxParser.parse(is,personHandler); List<Person> persons = personHandler.getPersons(); for (Person p:persons){ System.out.println(p); } } }
1. 加載和執行
盡量將所有的<script>標簽放在</body>標簽之前,確保腳本執行前頁面已經完成了渲染,避免腳本的下載阻塞其他資源(例如圖片)的下載。
合并腳本,減少頁面中的<script>標簽
使用<script>標簽的defer和async屬性(兩者的區別見這里)
通過Javascript動態創建<script>標簽插入文檔來下載,其不會影響頁面其他進程
2.數據存取
由于作用域鏈的機制,訪問局部變量比訪問跨作用域變量更快,因此在函數中若要多次訪問跨作用域變量,則可以用局部變量保存。
避免使用with語句,其會延長作用域鏈
嵌套的對象成員會導致引擎搜索所有對象成員,避免使用嵌套,例如window.location.href
對象的屬性和方法在原型鏈的位置越深,訪問的速度也越慢
3.Dom編程
進行大段HTML更新時,推薦使用innerHTML,而不是DOM方法
HTML集合是一個與文檔中元素綁定的類數組對象,其長度隨著文檔中元素的增減而動態變化,因此避免在每次循環中直接讀取HTML集合的length,容易導致死循環
使用節點的children屬性,而不是childNodes屬性,前者訪問速度更快,且不包含空白文本和注釋節點。
瀏覽器的渲染過程包括構建DOM樹和渲染樹,當DOM元素的幾何屬性變化時,需要重新構造渲染樹,這一過程稱為“重排”,完成重排后,瀏覽器會重新繪制受影響的部分到屏幕中,這一過程稱為“重繪”。因此應該盡量合并多次對DOM的修改,或者先將元素脫離文檔流(display:none、文檔片段),應用修改后,再插入文檔中。
每次瀏覽器的重排時都會產生消耗,大多數瀏覽器會通過隊列化修改并批量執行來優化重排過程,可當訪問元素offsetTop、scrollTop、clientTop、getComputedStyle等一系列布局屬性時,會強制瀏覽器立即進行重排返回正確的值。因此不要在dom布局信息改變時,訪問這些布局屬性。
當修改同個元素多個Css屬性時,可以使用CssText屬性進行一次性修改樣式,減少瀏覽器重排和重繪的次數
當元素發生動畫時,可以使用絕對定位使其脫離文檔流,動畫結束后,再恢復定位。避免動畫過程中瀏覽器反復重排文檔流中的元素。
多使用事件委托,減少監聽事件
4.算法和流程控制
for循環和while循環性能差不多,除了for-in循環最慢(其要遍歷原型鏈)
循環中要減少對象成員及數組項的查詢次數,可以通過倒序循環提高性能
循環次數大于1000時,可運用Duff Devices減少迭代次數
switch比if-else快,但如果具有很多離散值時,可使用數組或對象來構建查找表
遞歸可能會造成調用棧溢出,可將其改為循環迭代
如果可以,對一些函數的計算結果進行緩存
5.字符串和正則表達式
進行大量字符串的連接時,+和+=效率比數組的join方法要高
當創建了一個正則表達式對象時,瀏覽器會驗證你的表達式,然后將其轉化為一個原生代碼程序,用戶執行匹配工作。當你將其賦值給變量時,可以避免重復執行該步驟。
當正則進入使用狀態時,首先要確定目標字符串的起始搜索位置(字符串的起始位置或正則表達式的lastIndex屬性),之后正則表達式會逐個檢查文本和正則模式,當一個特定的字元匹配失敗時,正則表達式會試著回溯到之前嘗試匹配的位置,然后嘗試其他路徑。如果正則表達式所有的可能路徑都沒有匹配到,其會將起始搜索位置下移一位,重新開始檢查。如果字符串的每個字符都經歷過檢查,沒有匹配成功,則宣布徹底失敗。
當正則表達式不那么具體時,例如.和[\s\S]等,很可能會出現回溯失控的情況,在js中可以應用預查模擬原子組(?=(pattern))\1來避免不必要的回溯。除此之外,嵌套的量詞,例如/(A+A+)+B/在匹配"AAAAAAAA"時可能會造成驚人的回溯,應盡量避免使用嵌套的量詞或使用預查模擬原子組消除回溯問題。
將復雜的正則表達式拆分為多個簡單的片段、正則以簡單、必需的字元開始、減少分支數量|,有助于提高匹配的效率。
setTimeout(function(){ process(todo.shift()); if (todo.length > 0) { setTimeout(arguments.callee, 25); } else { callback(); } })
setTimeout(function(){ let start = +new Date(); do { process(todo.shift()); } while(todo.length > 0 && (+new Date() - start) < 50) if (todo.length > 0) { setTimeout(arguments.callee, 25); } else { callback(); } })
WebWork
進行計算
Expires: Mon,28 Jul 2018 23:30:30 GMT
eval
、Function
進行雙重求值
Object
/Array
字面量定義,不要使用構造函數
if (i & 1) { className = 'odd'; } else { className = 'even'; }
Math
對象等
背景
這一個因為滾動條占據空間引起的bug, 查了一下資料, 最后也解決了,順便研究一下這個屬性, 做一下總結,分享給大家看看。
正文
昨天, 測試提了個問題, 現象是一個輸入框的聚焦提示偏了, 讓我修一下, 如下圖:
image.png
起初認為是紅框提示位置不對, 就去找代碼看:
<Input
// ...
onFocus={() => setFocusedInputName('guidePrice')}
onBlur={() => setFocusedInputName('')}
/>
<Table
data-focused-column={focusedInputName}
// ...
/>
代碼上沒有什么問題, 不是手動設置的,而且, 在我和另一個同事, 還有PM的PC上都是OK的:
image.png
初步判斷是,紅框位置結算有差異, 差異大小大概是17px, 但是這個差異是怎么產生的呢?
就去測試小哥的PC上看, 注意到一個細節, 在我PC上, 滾動條是懸浮的:
image.png
在他PC上, 滾動條是占空間的:
image.png
在他電腦上, 手動把原本的 overscroll-y: scroll 改成 overscroll-y: overlay 問題就結局了。
由此判定是: 滾動條占據空間 引起的bug。
overscroll-y: overlay
CSS屬性 overflow, 定義當一個元素的內容太大而無法適應塊級格式化上下文的時候該做什么。它是 overflow-x 和overflow-y的 簡寫屬性 。
/* 默認值。內容不會被修剪,會呈現在元素框之外 */
overflow: visible;
/* 內容會被修剪,并且其余內容不可見 */
overflow: hidden;
/* 內容會被修剪,瀏覽器會顯示滾動條以便查看其余內容 */
overflow: scroll;
/* 由瀏覽器定奪,如果內容被修剪,就會顯示滾動條 */
overflow: auto;
/* 規定從父元素繼承overflow屬性的值 */
overflow: inherit;
官方描述:
overlay 行為與 auto 相同,但滾動條繪制在內容之上而不是占用空間。 僅在基于 WebKit(例如,Safari)和基于Blink的(例如,Chrome或Opera)瀏覽器中受支持。
表現:
html {
overflow-y: overlay;
}
兼容性
沒有在caniuse上找到這個屬性的兼容性, 也有人提這個問題:
image.png
問題場景以及解決辦法
1. 外部容器的滾動條
這里的外部容器指的是html, 直接加在最外層:
html {
overflow-y: scroll;
}
手動加上這個特性, 不論什么時候都有滾動寬度占據空間。
缺點: 沒有滾動的時候也會有個滾動條, 不太美觀。
優點: 方便, 沒有兼容性的問題。
2. 外部容器絕對定位法
用絕對定位,保證了body的寬度一直保持完整空間:
html {
overflow-y: scroll; // 兼容ie8,不支持:root, vw
}
:root {
overflow-y: auto;
overflow-x: hidden;
}
:root body {
position: absolute;
}
body {
width: 100vw;
overflow: hidden;
}
3. 內部容器做兼容
.wrapper {
overflow-y: scroll; // fallback
overflow-y: overlay;
}
總結
個人推薦還是用 overlay, 然后使用scroll 做為兜底。
內容就這么多, 希望對大家有所啟發。
文章如有錯誤, 請在留言區指正, 謝謝。
之前花了些時間將gatsby-theme-gitbook遷移到 Typescript,以獲得在 VSCode 中更好的編程體驗.
整體差不多已經完成遷移,剩下將 Gatsby 的 API 文件也遷移到 TS,這里可以看到 gatsby#21995 官方也在將核心代碼庫遷移到 Typescript,準備等待官方將核心代碼庫遷移完成,在遷移 API 文件.
這篇文章用XYShaoKang/gatsby-project-config,演示如何將 gatsby 遷移到 TypeScript,希望能幫到同樣想要在 Gatsby 中使用 TS 的同學.
遷移步驟:
TS 配置
配置 ESLint 支持 TS
完善 GraphQL 類型提示
初始化項目
gatsby new gatsby-migrate-to-typescript XYShaoKang/gatsby-project-config
cd gatsby-migrate-to-typescript
yarn develop
TS 配置
安裝typescript
添加typescript.json配置文件
修改 js 文件為 tsx
補全 TS 聲明定義
安裝typescript
yarn add -D typescript
添加配置文件tsconfig.json
// https://www.typescriptlang.org/v2/docs/handbook/tsconfig-json.html
{
"compilerOptions": {
"target": "esnext", // 編譯生成的目標 es 版本,可以根據需要設置
"module": "esnext", // 編譯生成的目標模塊系統
"lib": ["dom", "es2015", "es2017"], // 配置需要包含的運行環境的類型定義
"jsx": "react", // 配置 .tsx 文件的輸出模式
"strict": true, // 開啟嚴格模式
"esModuleInterop": true, // 兼容 CommonJS 和 ES Module
"moduleResolution": "node", // 配置模塊的解析規則,支持 node 模塊解析規則
"noUnusedLocals": true, // 報告未使用的局部變量的錯誤
"noUnusedParameters": true, // 報告有關函數中未使用參數的錯誤
"experimentalDecorators": true, // 啟用裝飾器
"emitDecoratorMetadata": true, // 支持裝飾器上生成元數據,用來進行反射之類的操作
"noEmit": true, // 不輸出 js,源映射或聲明之類的文件,單純用來檢查錯誤
"skipLibCheck": true // 跳過聲明文件的類型檢查,只會檢查已引用的部分
},
"exclude": ["./node_modules", "./public", "./.cache"], // 解析時,應該跳過的路晉
"include": ["src"] // 定義包含的路徑,定義在其中的聲明文件都會被解析進 vscode 的智能提示
}
將index.js改成index.tsx,重新啟動服務,查看效果.
其實 Gatsby 內置了支持 TS,不用其他配置,只要把index.js改成index.tsx就可以直接運行.添加 TS 依賴是為了顯示管理 TS,而tsconfig.json也是這個目的,當我們有需要新的特性以及自定義配置時,可以手動添加.
補全 TS 聲明定義
打開index.tsx,VSCode 會報兩個錯誤,一個是找不到styled-components的聲明文件,這個可以通過安裝@types/styled-components來解決.
另外一個錯誤綁定元素“data”隱式具有“any”類型。,這個錯誤是因為我們在tsconfig.json中指定了"strict": true,這會開啟嚴格的類型檢查,可以通過關閉這個選項來解決,只是我們用 TS 就是要用它的類型檢查的,所以正確的做法是給data定義類型.
下面來一一修復錯誤.
安裝styled-components的聲明文件
yarn add -D @types/styled-components
修改index.tsx
import React, { FC } from 'react'
import styled from 'styled-components'
import { graphql } from 'gatsby'
import { HomeQuery } from './__generated__/HomeQuery'
const Title = styled.h1`
font-size: 1.5em;
margin: 0;
padding: 0.5em 0;
color: palevioletred;
background: papayawhip;
`
const Content = styled.div`
margin-top: 0.5em;
`
interface PageQuery {
data: {
allMarkdownRemark: {
edges: Array<{
node: {
frontmatter: {
title: string
}
excerpt: string
}
}>
}
}
}
const Home: FC<PageQuery> = ({ data }) => {
const node = data.allMarkdownRemark.edges[0].node
const title = node.frontmatter?.title
const excerpt = node.excerpt
return (
<>
<Title>{title}</Title>
<Content>{excerpt}</Content>
</>
)
}
export default Home
export const query = graphql`
query HomeQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
title
}
excerpt
}
}
}
}
`
這時候會出現一個新的錯誤,在excerpt: string處提示Parsing error: Unexpected token,這是因為 ESLint 還無法識別 TS 的語法,下面來配置 ESLint 支持 TS.
配置 ESLint 支持 TypeScript
安裝依賴
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
配置.eslintrc.js
module.exports = {
parser: `@typescript-eslint/parser`, // 將解析器從`babel-eslint`替換成`@typescript-eslint/parser`,用以解析 TS 代碼
extends: [
`google`,
`eslint:recommended`,
`plugin:@typescript-eslint/recommended`, // 使用 @typescript-eslint/eslint-plugin 推薦配置
`plugin:react/recommended`,
`prettier/@typescript-eslint`, // 禁用 @typescript-eslint/eslint-plugin 中與 prettier 沖突的規則
`plugin:prettier/recommended`,
],
plugins: [
`@typescript-eslint`, // 處理 TS 語法規則
`react`,
`filenames`,
],
// ...
}
在.vscode/settings.json中添加配置,讓VSCode使用ESLint擴展格式化ts和tsx文件
// .vscode/settings.json
{
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[javascriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
完善 GraphQL 類型提示
// index.tsx
import React, { FC } from 'react'
// ...
interface PageQuery {
data: {
allMarkdownRemark: {
edges: Array<{
node: {
frontmatter: {
title: string
}
excerpt: string
}
}>
}
}
}
const Home: FC<PageQuery> = ({ data }) => {
// ...
}
export default Home
export const query = graphql`
query HomeQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
title
}
excerpt
}
}
}
}
`
我們看看index.tsx文件,會發現PropTypes和query結構非常類似,在Gatsby運行時,會把query查詢的結果作為組件prop.data傳入組件,而PropTypes是用來約束prop存在的.所以其實PropTypes就是根據query寫出來的.
如果有依據query自動生成PropTypes的功能就太棒了.
另外一個問題是在query中編寫GraphQL查詢時,并沒有類型約束,也沒有智能提示.
總結以下需要完善的體驗包括:
GraphQL 查詢編寫時的智能提示,以及錯誤檢查
能夠從 GraphQL 查詢生成對應的 TypeScript 類型.這樣能保證類型的唯一事實來源,并消除 TS 中冗余的類型聲明.畢竟如果經常需要手動更新兩處類型,會更容易出錯,而且也并不能保證手動定義類型的正確性.
實現方式:
通過生成架構文件,配合Apollo GraphQL for VS Code插件,實現智能提示,以及錯誤檢查
通過graphql-code-generator或者apollo生成 TS 類型定義文件
如果自己去配置的話,是挺耗費時間的,需要去了解graphql-code-generator的使用,以及Apollo的架構等知識.
不過好在社區中已經有對應的 Gatsby 插件集成了上述工具可以直接使用,能讓我們不用去深究對應知識的情況下,達到優化 GraphQL 編程的體驗.
嘗試過以下兩個插件能解決上述問題,可以任選其一使用
gatsby-plugin-codegen
gatsby-plugin-typegen
另外還有一款插件gatsby-plugin-graphql-codegen也可以生成 TS 類型,不過配置略麻煩,并且上述兩個插件都可以滿足我現在的需求,所以沒有去嘗試,感興趣的可以嘗試一下.
注意點:
Apollo不支持匿名查詢,需要使用命名查詢
第一次生成,需要運行Gatsby之后才能生成類型文件
整個項目內不能有相同命名的查詢,不然會因為名字有沖突而生成失敗
下面是具體操作
安裝vscode-apollo擴展
在 VSCode 中按 Ctrl + P ( MAC 下: Cmd + P) 輸入以下命令,按回車安裝
ext install apollographql.vscode-apollo
方式一: 使用gatsby-plugin-codegen
gatsby-plugin-codegen默認會生成apollo.config.js和schema.json,配合vscode-apollo擴展,可以提供GraphQL的類型約束和智能提示.
另外會自動根據query中的GraphQL查詢,生成 TS 類型,放在對應的tsx文件同級目錄下的__generated__文件夾,使用時只需要引入即可.
如果需要在運行時自動生成 TS 類型,需要添加watch: true配置.
安裝gatsby-plugin-codegen
yarn add gatsby-plugin-codegen
配置gatsby-config.js
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-codegen`,
options: {
watch: true,
},
},
],
}
重新運行開發服務生成類型文件
yarn develop
如果出現以下錯誤,一般是因為沒有為查詢命名的緣故,給查詢添加命名即可,另外配置正確的話,打開對應的文件,有匿名查詢,編輯器會有錯誤提示.
fix-anonymous-operations.png
這個命名之后會作為生成的類型名.
修改index.tsx以使用生成的類型
gatsby-plugin-codegen插件會更具查詢生成對應的查詢名稱的類型,保存在對應tsx文件同級的__generated__目錄下.
import { HomeQuery } from './__generated__/HomeQuery' // 引入自動生成的類型
// ...
// interface PageQuery {
// data: {
// allMarkdownRemark: {
// edges: Array<{
// node: {
// frontmatter: {
// title: string
// }
// excerpt: string
// }
// }>
// }
// }
// }
interface PageQuery {
data: HomeQuery // 替換之前手寫的類型
}
// ...
將自動生成的文件添加到.gitignore中
apollo.config.js,schema.json,__generated__能通過運行時生成,所以可以添加到.gitignore中,不用提交到 git 中.當然如果有需要也可以選擇提交到 git 中.
# Generated types by gatsby-plugin-codegen
__generated__
apollo.config.js
schema.json
方式二: 使用gatsby-plugin-typegen
gatsby-plugin-typegen通過配置生成gatsby-schema.graphql和gatsby-plugin-documents.graphql配合手動創建的apollo.config.js提供GraphQL的類型約束和智能提示.
根據GraphQL查詢生成gatsby-types.d.ts,生成的類型放在命名空間GatsbyTypes下,使用時通過GatsbyTypes.HomeQueryQuery來引入,HomeQueryQuery是由對應的命名查詢生成
安裝gatsby-plugin-typegen
yarn add gatsby-plugin-typegen
配置
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-typegen`,
options: {
outputPath: `src/__generated__/gatsby-types.d.ts`,
emitSchema: {
'src/__generated__/gatsby-schema.graphql': true,
},
emitPluginDocuments: {
'src/__generated__/gatsby-plugin-documents.graphql': true,
},
},
},
],
}
//apollo.config.js
module.exports = {
client: {
tagName: `graphql`,
includes: [
`./src/**/*.{ts,tsx}`,
`./src/__generated__/gatsby-plugin-documents.graphql`,
],
service: {
name: `GatsbyJS`,
localSchemaFile: `./src/__generated__/gatsby-schema.graphql`,
},
},
}
重新運行開發服務生成類型文件
yarn develop
修改index.tsx以使用生成的類型
gatsby-plugin-codegen插件會更具查詢生成對應的查詢名稱的類型,保存在對應tsx文件同級的__generated__目錄下.
// ...
// interface PageQuery {
// data: {
// allMarkdownRemark: {
// edges: Array<{
// node: {
// frontmatter: {
// title: string
// }
// excerpt: string
// }
// }>
// }
// }
// }
interface PageQuery {
data: GatsbyTypes.HomeQueryQuery // 替換之前手寫的類型
}
// ...
將自動生成的文件添加到.gitignore中
__generated__能通過運行時生成,所以可以添加到.gitignore中,不用提交到 git 中.當然如果有需要也可以選擇提交到 git 中.
# Generated types by gatsby-plugin-codegen
__generated__
藍藍設計的小編 http://www.syprn.cn