此篇我將會從函式的命名、註釋和魯棒性方面,闡述如何編寫高質量的函式。
PS:這一篇相對上一篇文章要簡單好懂多了。
引言
寫第二篇之前,先說個事情。針對前面我寫的 如何編寫高質量的函式 -- 敲山震虎篇 文章的評論區,小夥伴提出的一些問題,我也全部都看了,特此寫了答疑篇。
答疑篇放在了 issues
上,點選下面連結即可 TP
:
對我的回答有什麼疑問的話,可以 issues
討論,這篇文章就不放在掘金了。
PS: 提一下一些公眾號(掘金,奇舞週刊等)轉載的情況,由於文章一開始有錯誤,然後公眾號又沒有同步,然後我又不能幫你們改,於是點開公眾號文章,看著那些還在的錯誤,卻無能為力的心情,心裡默默
ob
: 感謝地主們的轉載,那就這樣吧?。
好,胡謅不多說,直接開始吧。
函式命名
從文章開頭的圖片可以知道,命名和快取是電腦科學中的兩大難題。
而今天要說的是函式命名,雖然函式命名涉及到的範圍較窄,但思想都是一樣的,完全 可以借鑑到其他的形式中。
我閱讀過程式碼大全的變數一章,也針對性的閱讀過一些原始碼,比如 lodash
, ramda
這些函式工具庫。現在根據我個人的一些感悟,總結了一些我個人認為能幫助你徹底解決命名這個事情的 best practice
。
PS: 雖然變數命名這個沒有所謂
best practice
,但是對於前端的函式命名來說,我個人認為是可以有一套完善的best pratice
的, 聽我娓娓道來。
目前前端的函式命名存在什麼問題
存在什麼問題呢?要我說啊,那些業界標準,比如駝峰,首字母大寫的類和建構函式,下劃線,$
等都不是瓶頸。真正的瓶頸是一些你察覺不到的,或者察覺到但是無能無力的細節。比如:
- 中英語言的差異性
- 不懂得從多維度去提升命名的準確性
- 不會使用輔助工具
下面進行簡明扼要的分析。
PS: 關於駝峰等耳熟能詳的業界標準我就不再提了。
漢語和英語的差異性
為什麼一開始要說這個呢,因為我認為這是目前命名中,存在的最大的問題。英語水平好的哥們沒多少,很多人不能掌握英語命名的那一套語法規則,說白了就是你英語水平達不到能像外國人那樣能寫出符合英語 style
的名字。
為什麼不能用漢語方式命名呢?
理由就三個:
- 用拼音的本質問題:漢語拼音存在多義性,想想就瑟瑟發抖。
- 用漢字的問題:雖然輔助工具已經很完善了,但是沒法普及,沒法和國際接軌,說白了就一句話,太小眾了,外國人沒有會的,不跟你玩。
- 鄙視鏈已經形成(emmmm)。
用英語時遇到的困難
最大的困難就是 不會 。
我舉個例子,都知道 react
的生命週期吧,如下:
-
componentDidMount
-
componentWillReceiveProps
-
shouldComponentUpdate
很多人都會有疑問,為什麼用 did
,為什麼用 will
。行吧,記住就完事了,然後過了一段時間,面試的被問到了,然後心裡 ob:
是 componentMounted
還是啥來...
多麼鮮活的例子,嗯,繼續往下讀吧,後面有驚(極)為(為)天(驚)人(悚)的答案。
如何讓命名有英語 style
黑人臉,怎麼讓啊?
老哥,多翻翻高中或者初中的英語語法知識吧。比如我舉個最簡單的例子,你就知道了。
componentDidMount
是react
等生命週期的鉤子,但是為什麼要這樣命名?
componentWillReceiveProps
為什麼要這樣命名?
答案就在下圖:
注意上圖中的 did
代表一般過去時,will
代表一般將來時。
然後我們百科一般過去式和一般將來時,然後如圖所示:
一般過去時:
一般將來時
看上圖的紅箭頭,did
表示一般過去時,時是指動作發生的時間,用在這裡,突出了鉤子的含義,一旦 mount
成功就執行此函式。是不是瞬間明白了,好了,will
同理。
啥也別說了,趕緊去好好看看初高中英語語法吧。
通過函式返回結果來命名
這是個小特性,比如 shouldComponentUpdate
, 為什麼 should
放在最前面。
因為這個函式返回的值是布林值。那麼我們可以理解為這是個問句,通過問句的形式來告訴我們,這裡具有不確定性,需要根據返回值來判斷是否更新。
關於問句的英語語法,老鐵們還要多翻翻語法書啊(淚)。
藉助工具
藉助谷歌翻譯
谷歌翻譯這個就不說了,大家都會
藉助 codelf
這是一個神器,用來搜尋各種開源專案中的變數命名,來給你提供參考。
對應名字的 VSCODE
外掛也有,具體怎麼用,小夥伴自行去了解吧。
如何避免函式命名的多義性和不可讀性
可能你給一個函式命名了,其他人看到這個函式時,一臉懵逼,完全不知道這命名的啥子東西喲,只能靠猜。
比如我從 ramda
原始碼中找了一個函式,程式碼如下:
var forEachObjIndexed = _curry2(function forEachObjIndexed(fn, obj) {
var keyList = keys(obj);
var idx = 0;
while (idx < keyList.length) {
var key = keyList[idx];
fn(obj[key], key, obj);
idx += 1;
}
return obj;
});
export default forEachObjIndexed;
複製程式碼
這個函式叫 forEachObjIndexed
,看到這個命名,是不是一臉懵逼,反正我第一次看到是懵逼了,這啥子嘛,什麼鬼東西,然後我就去趴了下原始碼,從原始碼裡面的函式註釋中才知道是幹啥的,函式註釋如下圖:
看到沒,多詳細,當然,這是為了輸出文件用的,但是給了我們一起非常好的解決方法。那就是:
如果你實在想不到如何去命名,或者你自己已經知道這個命名很爛了,比如太長,比如很難理解,那這個時候你就別掙扎了。寫一個你覺得還 ok
的命名,然後把剩下的時間留給你寫註釋吧。比如 forEachObjIndexed
的第一部分的註釋就是對整個函式的整體介紹和說明。
如果你的函式命名很爛,那這個時候,函式的整體介紹和說明就顯得非常重要了。這部分你一定要做好,英語水平不好的話,那就老老實實寫中文。這部分做好了,這個函式你哪怕用兩行文字命名的,或者用了火星文命名的,也沒關係,問題不大。
函式命名的分類
最後說一說命名的分類,這是我個人的一些看法。
為什麼我會說函式命名的分類呢,是因為我們經常會看到函式會這樣命名(原始碼中很普遍)。比如:
- $xxx()
- _xxx()
複製程式碼
這種帶各種字首的函式名,看起來並不好看。這樣命名,在我個人看起來是非常彆扭的,但是為什麼要有這種命名呢,其實這是前端的無奈之舉。
核心原因就是
JS
語言不支援私有變數,導致只能使用_
或者$
來保證相應的對外不可見,通過治標不治本的方法來解決這個問題。
所以我把前端的函式命名分為兩大類,如下:
第一類:不想暴露給外部訪問的函式(比如只給內部使用)
第二類:暴露給外部訪問的函式(各種功能方法)
我個人目前的觀點,大致也就這兩大類了。
PS:這裡我沒把 Symbol 初始化的函式命名考慮在內,比如如下程式碼:
const ADD = Symbol('add')
[ADD](a, b) {
console.log('a + b')
}
複製程式碼
關於 Symbol
的用法,大家可以自行了解,這種特例我就不考慮在內了。
PS:關於這個無奈之舉,在瞭解的更多的時候,會發現在前端,並沒有什麼方法(設計模式也好,
hack
方法也好)能絕對的解決上面的問題,所以有時候你不得不使用_
等,因為當都不能解決這個問題的時候,那越簡單的方式越受歡迎,這就是現實。
總結
總結一下最佳實踐:
多學習初中高中英語語法,開源專案中的函式命名沒有那麼難理解,通過語法的學習和藉助工具,函式命名基本可以解決,如果遇到無法清晰描述所寫函式的目的的命名時,請務必給函式寫上良好註釋,不管函式名字有多長多難懂,只要有良好的註釋,那就是可以接受的一件事情。畢竟你也不想命名的這麼難懂啊,但是能力有限,那就用漢語做好註釋吧,這樣的效果也是槓槓的。
如何通過良好的函式命名來提供函式的質量,我也說的差不多了,答案都在文字中,如何去借助工具,如何去理解英語中的命名語法,如何去通過多維度來增加命名含義的準確性和可讀性。簡單聊了下目前前端界函式命名的分類,大家自行體會和運用吧。
PS:一些都知道的點我就不說了,比如動詞+名詞,名詞+動詞,駝峰等,清晰描述函式的目的,這都不是痛點,痛點我都說了,最佳實踐也說了。
函式的註釋
我們來談函式的註釋,註釋一方面提高了可讀性,另一方面也可以通過註釋去做一些其它的事情,比如生成線上文件。一個高質量的函式,註釋少不了的,但是這並不代表所有的函式都需要註釋。富有富的活法,窮有窮的瀟灑,重要或者說複雜的函式,那就給個好註釋,簡單或者不重要的函式,可以不給註釋或者給一個簡單的註釋。說空話沒有意義,我們來看看目前函式的註釋都有哪幾種方式。
PS:這裡要注意我上面的用詞,如果你覺得這個函式命名很爛,那你就應該給一個好的註釋。
先說一些有名的npm包的一些註釋風格
就像大學裡面寫論文之前,都要閱讀很多文獻資料,我們也一樣,我們來看看幾個有名的 npm
包是怎麼玩註釋的。
egg.js 的註釋風格
從圖中,我們看到 egg.js
的入口檔案的註釋情況,暫且不去判斷這是不是一種 doc
工具的註釋規則(不要在意細節)。我們就看一下其註釋特點,是不是發現和你腦海中的註釋風格又有區別了呢。這種入口檔案的註釋特點,簡單整潔,這種思想是不是要吸收一波,以後你做開源專案的時候,這些思想都可以帶給你靈感。
繼續看下圖:
這是一個被抽象出來的基類,展示了作者 [Yiyu He
] 當時寫這個類的時候,其註釋的風格。從這張圖中,我們能學到什麼呢?有以下幾點:
第一點:建構函式的註釋規則,表示式語句的註釋規則。
第二點:註釋的取捨,有一些變數可以不用註釋,有些要註釋,不要有那種要註釋就要全部註釋的思想。
再看兩張有趣的圖片:
看上面兩張圖的箭頭,指向的都是同一個作者 [fengmk2
] , 我們看他的函式註釋規則。體會一下不同,想想為什麼第一張圖沒有空格,第二種有空格,還有對返回的 this
的註釋,比如很多人習慣將 this
直接註釋成 Object
型別。
lodash.js
說到函式註釋,就不能不說到 lodash.js
。但是寫到這,我發現這塊要是加上去的話,第二篇的文字就又超了,那這裡就不再說了,大家自己看看原始碼分析一下吧(這操作真香)。
通過註釋生成線上文件的思考
有人說註釋要很規範,方便給別人,比如用 jsdoc
等 。這裡我個人的看法是這樣的,對一些不需要開源的 web
專案,沒有必要用 jsdoc
, 理由如下:
- 繁瑣,需要按照
jsdoc
規則來 - 個人認為,
jsdoc
有入侵性,文件規則需要寫在程式碼中。
這裡我認為如果要寫註釋說明手冊,對於大型專案,我推薦使用 apidoc
, 因為 apidoc
入侵性不強,不要求把規則寫在程式碼中,你可以把所有規則寫到一個檔案中。具體使用方法,我就不說了,自行搜尋相關資料。
但是一般小專案,沒有必要單獨寫一份 api
文件。如果是開源的大型專案,那你要考慮的事情就更多了,首先需要有開源的官方網站,你會看到網上的一些開源專案官網好像很酷,其實這個世界上不缺的就是輪子,你也可以很快的做出這樣的網站,下面我們來看看是如何做到的。
首先我們看一下 taro
原始碼,會發現如下圖:
這裡就是生成一個靜態網站的祕密,執行這個 npm run docs
就可以了。用到的是 docusaurus
包,不知道的可以自行搜尋。
然後這裡你看下圖:
從圖中可以知道,文件的內容,來源於 docs
目錄,裡面都是 md
檔案,開源專案的文件說明都在這裡。
當然也有把對應的文件直接放到對應的程式碼目錄下的,比如 ant-design
如下圖:
就是直接把文件放在元件目錄下了。
從這裡,我們可以知道,目前流行的開源專案的官方網站是怎麼實現的,以及文件該怎麼寫。你可以說這和函式註釋沒有什麼關係,但是想想好像又有點關係,這裡就不多言了,自己體會吧。
我個人的註釋習慣
下面說說我本人對函式註釋(只針對函式註釋)的一些個人風格或者意見。
分享 VSCode
關於註釋的幾個工具
Better Comments
給註釋上色Document This
自動生成註釋TODO Highlight
高亮TODO
,並可以搜尋所有TODO
具體用法就不說了,下面是一張演示圖,自行去研究吧:
寫和不寫註釋的平衡
我個人的觀點是這樣的:
不影響可讀性,複雜度低,對外界沒有過度干涉的函式可以不寫註釋。
表示式語句的註釋
函式內,表示式語句的註釋可以簡單點,我一般如下圖所示,//
後面加簡要說明。
function add(a, b) {
// sum ....
let sum = a + b
}
複製程式碼
TODO 註釋
function say() {
// TODO: 編寫 say 具體內容
console.log('say')
}
複製程式碼
FIXME 註釋
function fix() {
// FIXME: 刪除 console.log方法
console.log('fix')
}
複製程式碼
函式註釋
一般我分為普通函式和建構函式。
普通函式註釋:
/**
* add
* @param {Number} a - 數字
* @param {Number} b - 數字
* @returns {Number} result - 兩個整數之和
*/
function add(a, b) {
// FIXME: 這裡要對 a, b 引數進行型別判斷
let result = a + b
return (result)
}
複製程式碼
建構函式註釋:
class Kun {
/**
* @constructor
* @param {Object} opt - 配置物件
*/
constructor(opt = {}) {
// 語句註釋
this.config = opt
}
}
複製程式碼
總結
從開源專案的程式碼中可以發現,註釋的風格多種多樣,有時候我自己不同專案的註釋風格也有點差別,但是我會盡可能的去平衡註釋和不註釋,上面註釋的基本原則還是要遵守的。
但是怎麼說呢,註釋這塊不存在銀彈。
函式的魯棒性(防禦性程式設計)
大家都聽過防禦性程式設計對吧,let it crash
。 我們看一個段子,下圖:
看最後一句,測試測了那麼多場景,最後酒吧還是炸了(哈哈哈哈哈哈怎麼回事?)。
所以,我們可以看出,防禦性程式設計的核心就是:
把所有可能會出現的異常都考慮到,並且做相應處理。
但是我個人認為,防禦性的程度要看其重要的程度。一般來說,不可能去處理所有情況的,但是提高程式碼魯棒性的有效途徑就是進行防禦性的程式設計。
一個專案的思考
我接手過一個需求,重寫(完全重構)蘇寧易購微信小程式的登入註冊繫結的功能,並將程式碼同步到蘇寧其他小程式(和其他小程式的開發進行程式碼交接並協助 coder
平穩完成版本過渡)。
這個專案重要性不言而喻,由於使用者的基數很大,風險程度很高,需要考慮很多場景,比如:
-
支不支援線上版本回退,也就是需要有前端的
AB
版本方案(線上有任何問題,可以快速切到舊登入方案) -
需要有各種驗證:圖形驗證碼、簡訊驗證碼、
ip
、人機、裝置指紋、風控、各種異常處理、異常埋點上報等。 -
程式碼層面的考慮:通過程式碼優化,縮短總的響應時間,提高使用者體驗。
-
如何確保單個節點出問題,不會影響整個登入流程。
你會發現,需要考慮的點很多,如何去合理的完成這個需求還是比較有難度的。
PS: 關於第四點的如何確保單個節點出問題,不會影響整個登入流程,文末有答案。
下面我就關於函式魯棒性,說一說我個人的一些看法。
前端函式魯棒性的幾種方式
入參要魯棒性
在 ES6
的到來後,函式的入參寫法已經得到了質的提高和優化。看下面程式碼
function print(obj = {}) {
console.log('name', obj.name)
console.log('age', obj.age)
}
複製程式碼
print
函式,入參是 obj
通過 obj = {}
來給入參設定預設的引數值,從而提高入參的魯棒性。
但是你會發現,如果入參的預設值是 {}
,那函式裡面的 obj.name
就會是 undefined
,這也不夠魯棒,所以下面就要說說函式內表示式語句的魯棒性了。
函式內表示式語句要魯棒性
繼續上個例子:
function print(obj = {}) {
console.log('name:', obj.name || '未知姓名')
console.log('age:', obj.age || '未知年齡')
}
複製程式碼
如果這樣的話,那你會發現表示式語句就變得比較魯棒性了,但是還不夠好,這樣寫不夠抽象,我們換種方式稍微把表示式語句給解耦一下,程式碼如下:
function print(obj = {}) {
const { name = '未知姓名', age = '未知年齡' } = obj
console.log('name:', name)
console.log('age:', age)
}
複製程式碼
這樣的話,看起來就感覺好多了,其實還可以再抽象,比如吧 console.log
封裝成 log
函式,通過呼叫 log(name)
,就能完成 console.log('name:', name)
的功能, 這裡就不再說了,自行研究吧。
函式異常處理的兩個層面
上面的那幾個點,我個人認為可以歸類到一個方案層面去,那就是:
防患於未然,從一開始就不要讓異常發生。
但是不要忘了,總會有萬一,還有一個方案層面要去考慮,那就是:
異常還是出現了,該怎麼去處理出現的異常。
下面兩個層面已經確定了,那如何去更好的處理各種異常,提高函式的魯棒性呢,我個人有以下幾點看法。
推導一下 try/catch
的原理
有很多人不清楚怎麼去用 try/catch
。這裡我來按照我個人的見解,來推一下其原理吧,首先 js
是執行在 node.js
提供的執行時環境中的,而 node.js
是用 C++
寫的。C++
是有自己的異常處理機制的,也是有 try/catch
的 。那就說明 js
的 try/catch
的底層實現是直接通過橋,呼叫 C++
的 try/catch
。
而 C++
的 try/catch
具有的一些特性,大家可以自行去了解一下,比如其中就有一個特性是這樣的:
try/catch
只能捕捉當前執行緒的異常。
所以這也就很好的解釋了,為什麼 JS
的 try/catch
只能捕捉到同步的異常,而對於非同步的異常就無能為力了(因為非同步是放在另一個執行緒中執行的)。
這裡是我的推導,不代表確切答案。
這裡我推薦一篇部落格:
有興趣的可以看看。
合理的處理異常
這裡有幾個方法:
第一個方法:如果是同步的操作,可以用
throw
來傳遞異常
看下面程式碼:
try {
throw new Error('hello godkun, i am an Error ')
console.log('throw 之後的處程式碼不執行')
} catch (e) {
console.log(e.message)
}
複製程式碼
首先我們要知道 throw
是以同步的方式傳遞異常的,也就是 throw
要和使用 throw
傳遞錯誤的函式擁有相同的上下文環境。
如果上下文環境中,都沒有使用 try/catch
的話,但是又 throw
了異常,那麼程式大概率會崩潰。
如果是 nodejs
,這個時候就應該再加一個程式級的 uncaughtException
和 來捕捉這種沒有被捕捉的異常。通常還會加上 unhandledRejection
的異常處理。
第二個方法:如果是非同步的操作
有三種方式:
-
使用
callback
,比如nodejs
的error first
風格 -
對於複雜的情況可以使用基於
Event
的方式來做,呼叫者來監聽物件的error
事件 -
使用
promise
和async/await
來捕捉異常
現在的問題是,怎麼去選擇哪個方式呢?有這幾個原則:
-
簡單的場景,直接使用
promise
和async/await
來捕捉異常 -
複雜的場景,比如可能會產生多個錯誤,這個時候最好用
Event
的方式
第三個方法:如果既有非同步操作又有同步操作
怎麼辦呢?這個時候,我個人認為,最好的方式,就是使用最新的語法:async/await
來結合 promise
和 try/catch
來完成對既有同步操作又有非同步操作的異常捕捉。
第四個方法:處理異常的一些抽象和封裝
對處理異常的函式進行抽象和封裝也是提高函式質量的一個途徑。如何對處理異常進行抽象和封裝呢?有幾個方式可以搞定它:
-
第一個方式:對
nodejs
來說,通常將異常處理封裝成中介軟體,比如基於express/koa
的異常中介軟體,通常情況下,處理異常的中介軟體要作為最後一箇中介軟體載入,目的是為了捕獲之前的所有中介軟體可能出現的錯誤 -
第二個方式:對前端或者
nodejs
來說,可以將異常處理封裝成模組,類似Event
的那種。 -
第三種方式:使用裝飾器模式,對函式裝飾異常處理模組,比如通過裝飾器對當前函式包裹一層
try/catch
。 -
第四種方式:使用函數語言程式設計中的函子(
Monad
)等來對異常處理進行統一包裹,這裡 的Monad
和try/catch
在表現上都相當於一個容器,這是一個相當強大的方法。從Monad
可以擴充套件出很多異常處理的黑科技,但是我建議慎用,因為不是所有人都能看懂的,要考慮團隊的整體技術能力,當然一個人的話,那就隨便嗨了。
合理的處理異常,需要能確定使用哪一種方式來處理異常,我大致也說了具體的選擇情況,這裡我推薦一篇部落格:
目前我見到的講的最全的處理異常的部落格,但我這裡說的都是我認為比較重要的主要的點,兩者還是有明顯區別的,大家融合一下吸收吸收吧。
如何確保單個節點出問題,不會影響整個登入流程
比如登入流程需要4個安全驗證,按照通常的寫法,其中一個掛了,那就全部掛了,但是這不夠魯棒性,如何去解決這個問題呢。這裡我提一下,可能很多人都不會注意到。
主要方案就使用將 promise
的鏈式寫法換一種方式寫,以前的寫法是這樣的:
虛擬碼如下:
auth().then(getIP).then(getToken).then(autoLogin).then(xxx).catch(function(){})
複製程式碼
經過魯棒調整後,可以改成如下寫法:
虛擬碼如下:
auth().catch(goAuthErrorHandle).then(getIP).catch(goIPErrorHandle).then(function(r){})
複製程式碼
經過微調後的程式碼,直接讓登入流程的魯棒性提升了很多,就算出錯也可以通過錯誤處理後,繼續傳遞到下一個方法中。
我個人對異常處理的看法
我個人認為對異常的處理,還是要根據實際情況來分析的。大概有以下幾點看法:
要考慮專案可維護性,團隊技術水平
我曾在一個需求中,使用了諸如函子等較為抽象的處理異常的方法,雖然秀了一把(作死),結果導致,後續這塊的需求改動,還得我自己來。嗯,就是這麼刺激,因為同事不熟悉函數語言程式設計。
要提前預估好專案的複雜性和重要性。
比如在做一個比較重要的業務時,一開始沒有想到異常處理需要這麼細節,而且一般第一版的時候,需求並沒有涉及到很多異常情況處理,但是後續需求迭代優化的時候,發現異常情況處理是如此的多,直接導致需要重寫異常處理相關的程式碼。
所以以後在專案評估的時候,要學會嘗試根據專案的重要性,來提前預留好坑位。
這也算是一種面對未來的程式設計模式。
總結
關於函式的魯棒性(防禦性程式設計),我介紹了很多東西,基本上是前端或者是 nodejs
處理異常的常規方法吧。處理異常不是一個簡單的活,工作中還得結合業務去確定合適的異常處理方式,總之,多多實踐出真知吧。
備註
- 本篇文章閱讀起來就比較輕鬆了,難度不大,細細體會一下會有一些收穫的。
- 可能講解的不全,如果有什麼遺漏的,歡迎在評論區分享,一起進步。
- 魯棒性這塊,我沒有提單元測試,字數不夠寫了,再寫文章又很長了,單元測試我只推薦
Jest
,按照官網文件來,本著函數語言程式設計的思想,問題不大。
往屆精品文章
可以這麼說,讀了這篇文章,你的 git
和 gerrit
就沒有什麼問題。
關於如何閱讀 npm
包原始碼的故事。
彩蛋
最近剛寫的一篇文章,發現沒什麼閱讀量,但是我覺得我寫的很好啊,這篇文章絕對會對絕大多數前端工程師有所啟發和幫助。
宣傳一波,想啟發更多還在路上的前端小夥伴:
交流
加上本篇,這個系列已經寫了兩篇了,我計劃三篇搞定,但是看這情況,後面都是硬貨,很難 7000
字以內搞定呀,後續幾篇搞定,我再 ob
一下吧。
小夥伴們可以關注我的掘金部落格或者 github
來獲取後續的系列文章更新通知。
掘金系列技術文章在 github
上彙總如下,覺得不錯的話,點個 star 鼓勵一下,我將 使命必達 ,持續輸出精品文章。
我是原始碼終結者,歡迎技術交流。
也可以進 前端狂想錄群 大家一起頭腦風暴。有想加的,因為人滿了,可以先加我好友,我來邀請你進群。
風之語
最後:尊重原創,轉載請註明出處哈?