大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。
本文來自讀者Ethan01
投稿,寫了axios
源碼中的工具函數~非常值得一學。原文鏈接:https://juejin.cn/post/7042610679815241758
1.前言
歌德說過:讀一本好書,就是在和高尚的人談話。
同理,讀優秀的開源項目的源碼,就是在和牛逼的大佬交流。
之前總覺得閱讀源碼是一件了不起的事情,是只有大佬才會去做的事。其實源碼也不是想象的那么難,至少有很多看得懂。 比如源碼中的工具函數,就算是初級的前端開發也是能夠看懂的。重要的是,要邁出這一步,閱讀源碼沒什么的。
閱讀本文,你將學到:
1、javascript、nodejs調試技巧及調試工具;
2、如何學習調試axios源碼;
3、如何學習優秀開源項目的代碼,應用到自己的項目;
4、axios源碼中實用的工具函數;
2.環境準備
2.1 讀開源項目的貢獻指南
打開 axios[1] , 你會驚奇的發現,這不是在瀏覽器中打開了一個vscode
嗎?你沒有看錯,確實是在瀏覽器中打開了vscode
,而且還打開了axios
的源碼。如果你仔細看了瀏覽器地址欄里的url
, 你會發現github
后多了1s
,顧名思義,就是1s
打開github
上的項目。一個小擴展:在每一個github
項目中的url
里直接加上1s
,就能在網頁版vscode
中查看源碼了(不過貌似現在只能查看,不能調試,調試的話還是要把源碼clone
到本地)。
開源項目一般能在根目錄下的README.md
文件或CONTRIBUTING.md
中找到貢獻指南。貢獻指南中說明了參與貢獻代碼的一些注意事項,比如:代碼風格、代碼提交注釋格式、開發、調試等。
打開CONTRIBUTING.md[2],可以看到在54
行的內容:
Running?sandbox?in?browser```bash
$?npm?start
#?Open?127.0.0.1:3000
這里就是告訴我們在如何在瀏覽器中運行項目的。
2.2 克隆項目并運行
這里使用axios
的版本是v0.24.0
;
git?clone?https://github.com/axios/axios.gitcd?axiosnpm?start打開?http://localhost:3000/
這時候可以看到這么一個頁面:

打開瀏覽器的控制臺,選中source
選項,然后在axios
目錄中可以找到源碼,如下圖:

這個axios.js
就是入口文件,這時候就可以隨意打斷點進行調試了。
其實,閱讀所有源碼的流程都類似,之所以說的這么詳細,是為了能夠讓沒有閱讀過源碼的同學也能夠跟著一步一步的閱讀起來。當你讀完之后,肯定會有不少的收獲,把這個過程和收獲記錄下來,慢慢的提升自己,早晚會成為大佬。
3. 工具函數
今天的主角是`utils.js`[3]文件, 以下列出了文件中的工具函數:
3.1 isArray
判斷數組
var?toString?=?Object.prototype.toString;//?可以通過?`toString()`?來獲取每個對象的類型
//?一般返回值是?Boolean?類型的函數,命名都以?is?開頭
function?isArray(val)?{return?toString.call(val)?===?'[object?Array]';
}
3.2 isUndefined
判斷Undefined
//?直接用`typeof`判斷
//?注意?typeof?null?===?'object'
function?isUndefined(val)?{return?typeof?val?===?'undefined';
}
3.3 isBuffer
判斷 buffer
//?先判斷不是?`undefined`和`null`
//?再判斷?`val`存在構造函數,因為`Buffer`本身是一個類
//?最后通過自身的`isBuffer`方法判斷function?isBuffer(val)?{return?val?!==?null?&&?!isUndefined(val)?&&?val.constructor?!==?null?&&?!isUndefined(val.constructor)&&?typeof?val.constructor.isBuffer?===?'function'?&&?val.constructor.isBuffer(val);
}
什么是Buffer?
JavaScript 語言自身只有字符串數據類型,沒有二進制數據類型。
但在處理像TCP流或文件流時,必須使用到二進制數據。因此在 Node.js
中,定義了一個Buffer
類,該類用來創建一個專門存放二進制數據的緩存區。詳細可以看 官方文檔[4] 或 更通俗易懂的解釋[5]。
因為axios
可以運行在瀏覽器和node
環境中,所以內部會用到nodejs
相關的知識。
3.4 isFormData
判斷FormData
//?`instanceof`?運算符用于檢測構造函數的?`prototype`?屬性是否出現在某個實例對象的原型鏈上function?isFormData(val)?{return?(typeof?FormData?!==?'undefined')?&&?(val?instanceof?FormData);
}//?instanceof?用法function?C()?{}
function?D()?{}const?c?=?new?C()c?instanceof?C?//?output:?true???因為?Object.getPrototypeOf(c)?===?C.prototypec?instanceof?Object?//?output:?true???因為?Object.prototype.isPrototypeOf(c)c?instanceof?D?//?output:?false???因為?D.prototype?不在?c?的原型鏈上
3.5 isObject
判斷對象
//?排除?`null`的情況
function?isObject(val)?{return?val?!==?null?&&?typeof?val?===?'object';
}
3.6 isPlainObject
判斷 純對象
純對象:用{}
或new Object()
創建的對象。
function?isPlainObject(val)?{if?(Object.prototype.toString.call(val)?!==?'[object?Object]')?{return?false;}var?prototype?=?Object.getPrototypeOf(val);return?prototype?===?null?||?prototype?===?Object.prototype;
}//?例子1
const?o?=?{name:?'jay}
isPlainObject(o)?//?true//?例子2
const?o?=?new?Object()
o.name?=?'jay'
isPlainObject(o)???//?true//?例子3
function?C()?{}
const?c?=?new?C()
isPlainObject(c);??//?false//?其實就是判斷目標對象的原型是不是`null`?或?`Object.prototype`
3.7 isDate
判斷Date
function?isDate(val)?{return?Object.prototype.toString.call(val)?===?'[object?Date]';
}
3.8 isFile
判斷文件類型
function?isFile(val)?{return?Object.prototype.toString.call(val)?===?'[object?File]';
}
3.9 isBlob
判斷Blob
function?isBlob(val)?{return?Object.prototype.toString.call(val)?===?'[object?Blob]';
}
Blob
對象表示一個不可變、原始數據的類文件對象。它的數據可以按文本或二進制的格式進行讀取。
3.10 isFunction
判斷函數
function?isFunction(val)?{return?Object.prototype.toString.call(val)?===?'[object?Function]';
}
3.11 isStream
判斷是否是流
//?這里`isObject`、`isFunction`為上文提到的方法
function?isStream(val)?{return?isObject(val)?&&?isFunction(val.pipe);
}
3.12 isURLSearchParams
判斷URLSearchParams
function?isURLSearchParams(val)?{return?typeof?URLSearchParams?!==?'undefined'?&&?val?instanceof?URLSearchParams;
}//?例子
const?paramsString?=?"q=URLUtils.searchParams&topic=api"
const?searchParams?=?new?URLSearchParams(paramsString);
isURLSearchParams(searchParams)?//?true
URLSearchParams
接口定義了一些實用的方法來處理 URL 的查詢字符串,詳情可看 MDN[6]:
var?paramsString?=?"q=URLUtils.searchParams&topic=api"
var?searchParams?=?new?URLSearchParams(paramsString);for?(let?p?of?searchParams)?{console.log(p);
}//?輸出?
[?'q',?'URLUtils.searchParams'?]
[?'topic',?'api'?]searchParams.has("topic")?===?true;?//?true
searchParams.get("topic")?===?"api";?//?true
searchParams.getAll("topic");?//?["api"]
searchParams.get("foo")?===?null;?//?true
searchParams.append("topic",?"webdev");
searchParams.toString();?//?"q=URLUtils.searchParams&topic=api&topic=webdev"
searchParams.set("topic",?"More?webdev");
searchParams.toString();?//?"q=URLUtils.searchParams&topic=More+webdev"
searchParams.delete("topic");
searchParams.toString();?//?"q=URLUtils.searchParams"
3.13 trim
去除首尾空格
//?`trim`方法不存在的話,用正則
function?trim(str)?{return?str.trim???str.trim()?:?str.replace(/^\s+|\s+$/g,?'');
}
3.14 isStandardBrowserEnv
判斷標準瀏覽器環境
function?isStandardBrowserEnv()?{if?(typeof?navigator?!==?'undefined'?&&?(navigator.product?===?'ReactNative'?||navigator.product?===?'NativeScript'?||navigator.product?===?'NS'))?{return?false;}return?(typeof?window?!==?'undefined'?&&typeof?document?!==?'undefined');
}
但是官方已經不推薦使用這個屬性navigator.product
。

3.15 forEach
遍歷對象或數組
保留了英文注釋,提升大家的英文閱讀能力。
/***?Iterate?over?an?Array?or?an?Object?invoking?a?function?for?each?item.*??用一個函數去迭代數組或對象**?If?`obj`?is?an?Array?callback?will?be?called?passing*?the?value,?index,?and?complete?array?for?each?item.*?如果是數組,回調將會調用value,?index,?和整個數組**?If?'obj'?is?an?Object?callback?will?be?called?passing*?the?value,?key,?and?complete?object?for?each?property.*?如果是對象,回調將會調用value,?key,?和整個對象**?@param?{Object|Array}?obj?The?object?to?iterate*?@param?{Function}?fn?The?callback?to?invoke?for?each?item*/function?forEach(obj,?fn)?{//?Don't?bother?if?no?value?provided//?如果值不存在,無需處理if?(obj?===?null?||?typeof?obj?===?'undefined')?{return;}//?Force?an?array?if?not?already?something?iterable//?如果不是對象類型,強制轉成數組類型if?(typeof?obj?!==?'object')?{obj?=?[obj];}if?(isArray(obj))?{//?Iterate?over?array?values//?是數組,for循環執行回調fnfor?(var?i?=?0,?l?=?obj.length;?i?<?l;?i++)?{fn.call(null,?obj[i],?i,?obj);}}?else?{//?Iterate?over?object?keys//?是對象,for循環執行回調fnfor?(var?key?in?obj)?{//?只遍歷可枚舉屬性if?(Object.prototype.hasOwnProperty.call(obj,?key))?{fn.call(null,?obj[key],?key,?obj);}}}
}
所以,源碼為什么不用forEach
和for...in...
呢???????
3.16 stripBOM
刪除UTF-8
編碼中BOM
/***?Remove?byte?order?marker.?This?catches?EF?BB?BF?(the?UTF-8?BOM)**?@param?{string}?content?with?BOM*?@return?{string}?content?value?without?BOM*/function?stripBOM(content)?{if?(content.charCodeAt(0)?===?0xFEFF)?{content?=?content.slice(1);}return?content;
}
所謂 BOM
,全稱是Byte Order Mark
,它是一個Unicode
字符,通常出現在文本的開頭,用來標識字節序。UTF-8
主要的優點是可以兼容ASCII
,但如果使用BOM
的話,這個好處就蕩然無存了。
4.總結
本文主要介紹了axios
源碼的調試過程,以及介紹了一些utils.js
中的非常實用的工具函數;相信通過閱讀源碼,日積月累,并把這些代碼或思想應用的自己項目中去,相信能夠很好的提升自己的編碼能力。
come on! worker!
同時也推薦一些好用的工具:
瀏覽器中運行`vscode`, 查看源碼[7]
代碼沙盒,能運行多種語言,且可以添加依賴[8]
vs code 的 code Runner插件[9]
參考資料
[1]
axios: https://github1s.com/axios/axios
[2]CONTRIBUTING.md: https://github1s.com/axios/axios/blob/HEAD/CONTRIBUTING.md
[3]utils.js
: https://github.com/axios/axios/blob/master/lib/utils.js
官方文檔: http://nodejs.cn/api/buffer.html#buffer
[5]更通俗易懂的解釋: https://www.runoob.com/nodejs/nodejs-buffer.html
[6]MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams
[7]瀏覽器中運行vscode
, 查看源碼: https://github1s.com/axios/axios
代碼沙盒,能運行多種語言,且可以添加依賴: https://codesandbox.io/
[9]vs code 的 code Runner插件: https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。分享、收藏、點贊、在看我的文章就是對我最大的支持~