- 作者:陳大魚頭
- github: [KRISACHAN] github.com/KRISACHAN
前言
最近有很多童鞋跟魚頭說,面試的時候動不動就問源碼。
也有很多童鞋遇到問題的時候,魚頭建議這些童鞋看相關庫 / 框架 / 項目的源碼。
但是也有很多童鞋向魚頭抱怨說:“源碼太難了。”
那麼源碼真的是一塊難啃的硬骨頭嗎?
其實不是的。
作爲一個優秀(或說合格)的開源項目,它的代碼一定不會是晦澀難懂的。不僅是代碼本身,這些項目配套的註釋,單元測試,示例代碼,函數名以及文檔一定是能夠很好地輔助你讀源碼的。
下面就讓魚頭來跟大家談談我自己的一些看源碼心得。
(注:這不是最佳實踐,只算是魚頭個人的經驗,不一定適用於所有人,如果你有不同的意見,歡迎在下方評論區域留言。)
正文
看配套說明
在看一個開源項目源碼之前,魚頭首先會先看其文檔,不一定是會細緻到各個API,但是會先理解這個項目的背景,思想,以及解決的問題是什麼。
舉個例子,我們來看看一個叫做[runtime-hooks] github.com/gaoding-inc/runtime-hooks 的開源工具庫。這裏先上部分代碼:
function withHookAfter (originalFn, hookFn) {
return function () {
var output = originalFn.apply(this, arguments)
hookFn.apply(this, arguments)
return output
}
}
這段函數我們是看懂了,就是劫持原函數,並且在原函數執行之後,執行我們自己的邏輯。但是如果沒有相關的業務經驗,我們不一定能理解爲什麼這麼幹,不理解就很容易會忘記。
但是我們可以看看相關文檔裏,作者對這個庫說明的部分節選:
假設有一個被業務廣泛使用的函數,我們是否能夠在既不更改調用它的業務代碼,也不更改該函數源碼的前提下,在其執行前後注入一段我們自定義的邏輯呢?
哦,那麼這麼說我們就理解了,通過這種方案,當我們需要入侵某個函數的時候,就不需要再進行一些複雜的hack,魔改動作了。
那麼我們就理解了這段函數的存在的意義,因爲理解,所以這段代碼,自然而然的就記住了,以後遇到類似需要的場景也能夠輕而易舉的相到這個方案。
看類型文件
現在有許多的開源項目都是用 TypeScript 來寫的,既然是如此,通常在根目錄下都會有一個.d.ts
文件專門定義API類型的。
例如我們來看看一個開源的Web IDE庫 [ace.js]
看看它根目錄下的ace.d.ts
裏的部分代碼:
export interface EventEmitter {
once(name: string, callback: Function): void;
setDefaultHandler(name: string, callback: Function): void;
removeDefaultHandler(name: string, callback: Function): void;
on(name: string, callback: Function, capturing?: boolean): void;
addEventListener(name: string, callback: Function, capturing?: boolean): void;
off(name: string, callback: Function): void;
removeListener(name: string, callback: Function): void;
removeEventListener(name: string, callback: Function): void;
}
通過上面的類型註解,我們很容易就能知道每個API的具體用法。
魚頭我爲什麼要拿這個來說呢,因爲有的時候要解決的問題,可能只是API熟悉的問題,但是有些開源項目的文檔寫的並不走心,上述的這個就是這樣,所以有的時候爲了瞭解一個API,又不想花太多時間去找具體源碼位置時,就可以打開類型文件來看看具體的API使用法則。
看註釋
在我們深入到某一個具體的函數或者文件時,如果我們能先知道它是幹啥的,那麼對於我們要理解這段代碼來說,是事半功倍的。
舉個例子,我們來看看[redux]。
在redux/src/createStore.js
的開頭有這麼一段註釋:
Creates a Redux store that holds the state tree.
The only way to change the data in the store is to call
dispatch()
on it.
創建一個保存狀態樹的Redux倉庫。
更改倉庫中數據的唯一方法是對其調用
dispatch()
。
哦,那麼通過上面的註釋,我們就知道dispatch
方法是用來對數據進行調度的。而且是唯一的一個改變數據的方法。
有意思的是這個文件約300行代碼裏,估計有100行是註釋,那麼當我們看完註釋之後,即使不看具體實現,也很容易明白它究竟做了什麼。
當我們再去看具體實現的時候,我們帶着“ 它主要是幹了這事 ”的想法去看,那麼對具體實現的理解就更輕鬆了。
看測試樣例
除了上述的幾個方法,我們還可以看測試樣例。其實測試樣例,對於我們理解源碼,或迅速上手一個陌生項目來說是非常高效的。
例如我們看[vuex]的vuex/test/unit/store.spec.js
裏的一個例子:
describe('Store', () => {
it('committing mutations', () => {
const store = new Vuex.Store({
state: {
a: 1
},
mutations: {
[TEST] (state, n) {
state.a += n
}
}
})
store.commit(TEST, 2)
expect(store.state.a).toBe(3)
})
it('dispatching actions, with returned Promise', done => {
const store = new Vuex.Store({
state: {
a: 1
},
mutations: {
[TEST] (state, n) {
state.a += n
}
},
actions: {
[TEST] ({ commit }, n) {
return new Promise(resolve => {
setTimeout(() => {
commit(TEST, n)
resolve()
}, 0)
})
}
}
})
expect(store.state.a).toBe(1)
store.dispatch(TEST, 2).then(() => {
expect(store.state.a).toBe(3)
done()
})
})
})
即使是不懂vuex的童鞋,通過上述的兩個樣例,我們也很容易就理解mutations
是同步的,而actions
是異步的。甚至依靠着這兩個樣例,我們也能輕鬆的上手vuex
了。
看官方例子
還有就是官方例子辣,其實魚頭髮現很多童鞋,在學一個庫/框架的時候,並不喜歡看官方例子,反而喜歡看網上各種教程,雖然看教程不是不好,但是如果本身該庫/框架就有官方例子,那麼再去找二手知識,就有點本末倒置了。
例如我們看[webpack]的webpack/examples/typescript
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
module.exports = (env = "development") => ({
mode: env,
entry: {
output: "./index.ts"
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
transpileOnly: true
}
}
]
},
resolve: {
extensions: [".ts", ".js", ".json"]
},
plugins: [new ForkTsCheckerWebpackPlugin({ async: env === "production" })]
});
就這樣,這是一個可以直接CV的typescript配置,多簡單,這樣就不需要再去網上找各種不知結果又添加了各種個人理解讓你蒙圈的教程了。
總結
以上的就是魚頭日常看源碼時會關注的一些點。
當然這只是魚頭的一些經驗總結,不一定適用於每個人。
而且魚頭水平有限,也不能保證100%無誤。
如果各位讀者有任何不同意見,或者自己的經驗,非常歡迎各位在下方留言區域留言。