深入探尋seajs的模組化與載入方式,探尋seajs

來源:互聯網
上載者:User

深入探尋seajs的模組化與載入方式,探尋seajs

由於一直在使用,所以瞭解了下seajs的原始碼。這裡是我對下面幾個問題的理解:

1、seajs的require(XXX)的方法是怎樣實現模組載入的?

2、為什麼需要預先載入?

3、為什麼需要構建工具?

4、構建前後的代碼究竟有些什麼區別,為什麼要這麼做?

問題1: seajs的require(XXX)的方法是怎樣實現模組載入的?

代碼邏輯比較繞,對原始碼的理解放在文章的末尾,這裡先簡單梳理下模組載入的邏輯:

1、從seajs.use方法入口,開始載入use到的模組。

2、use到的模組這時mod緩衝當中一定是不存在的。seajs建立一個新的mod,賦予一些初始的狀態。

3、執行mod.load方法

4、一堆邏輯之後走到seajs.request方法,請求模組檔案。模組載入完成之後,執行define方法。

5、define方法分析提模數塊的相依模組,儲存起來。緩衝factory但不執行。

6、模組的相依模組再被載入,如果繼續有相依模組,則繼續載入。直至所有被依賴的模組都載入完畢。

7、所有的模組載入完畢之後,執行use方法的callback.

8、模組內部邏輯從callback開始執行。require方法在這個過程當中才被執行。

問題2:為什麼需要預先載入?

我們看到seajs.use方法實際上是在所有相依模組都載入完了之後才執行callback。可以理解成在商務邏輯代碼在執行之前,必須先預先載入所有被依賴的模組代碼。那麼為什麼是一個這樣必須先做預先載入的邏輯?

答案在於邏輯代碼裡面引用其他模組方法的這個require方法的執行方法:

var mod = require(id);

這個文法決定了mod的取得是個同步執行的過程,如果模組代碼在此之前沒有被預先載入的話,就只能採用非同步載入回調的方法來實現了,那麼整個seajs的執行邏輯將完全會是另一個樣子。因為非同步你會搞不懂模組的執行順序,邏輯會變的難以掌控。

問題3:為什麼需要構建工具?

可以看到沒有構建前各個相依模組都是單獨載入的。這會產生過多的模組請求,對於頁面的載入效能是不利的。構建工具本質上就是為瞭解決模組合併載入的問題。

問題4:構建前後的代碼究竟有些什麼區別,為什麼要這麼做?

構建工具究竟做了些什麼。我們說它本質上是為瞭解決代碼合併載入的問題,那麼它所做的只是簡單的將各個模組檔案合并成一個檔案?

當然不是。測試一下,你如果只是簡單把幾個模組檔案合并到一個檔案以後,會發現這個檔案根本沒有辦法正常執行。

原因在於define方法的實現。

seajs是推崇定義模組的時候只在define方法傳入factory參數的。回顧define方法內部,當沒有傳入id(姑且等同於模組的url)時,會通過getCurrentScript()方法去取得當前正在執行的這個模組檔案的url路徑,然後把這個路徑作為索引值與模組本身一起緩衝到cachedMods。這裡很關鍵的一點是,整個seajs內部的這個模組緩衝機制其實是依賴每個模組的url來做緩衝的索引值。require(id)方法,歸根結底也是通過url索引值到。require(id)方法,歸根結底也是通過url索引值到cachedMods裡面去找相應的模組。這個索引值不能重複不能出錯,不然模組的對應關係就混亂了。如果把a、b、c幾個模組檔案簡單合并到一個目標檔案x之後,getCurrentScript()只能擷取到x的路徑,三個模組的索引值就沒法做出區別了,執行肯定出錯。

所以如果要把幾個模組檔案合并在一起,就必須為每個模組明確uri。也就是define方法必須都傳入id參數。當id傳入的時候,seajs會將這個id轉換為url用作緩衝的索引值。

如果只傳id和factory,也就是 define(id, factory),那麼deps = undefined,define方法就會去執行parseDependencies(factory.toString())方法提取factory裡面的相依模組,後續會走到解析模組路徑,線上單獨載入各個模組的邏輯裡面去,這個時候就失去了合併載入的意義了。

所以合併載入,define方法必須正確的傳入id,deps,factory三個參數才能正確執行。

seajs 所謂CMD的模組定義方法,是提倡大家寫模組的階段都只傳factory一個參數的,其他兩個參數在後期代碼構建的階段來產生。上面解釋了為什麼這兩參數在構建後是必須的。

至於為什麼提倡定義模組的時候只傳factory,我看主要是因為手工傳入的id和deps參數,極易出錯,不便維護。工具可以提高效率並保證參數的正確。

附: 對seajs 主要代碼邏輯的理解。

說明:原始碼版本是Sea.js 2.3.0

1、先看看define方法做了些什麼

Module.define = function (id, deps, factory)

define方法的時候,支援三個參數。其中id,deps是選填的。factory必須。代碼裡面通過以下邏輯來控制:

但其實deps是必須的,因為seajs必須知道每個模組依賴了哪些模組,不然無法執行載入。

所以,當factory是函數,並且deps沒有被主動傳入的時候,就需要使用parseDependencies方法來分析出factory當中的相依模組了。

parseDependencies方法做的事情主要就是用一個Regex把函數體裡面所有require(XXX)裡面的XXX提取出來,這也就是這個函數依賴到的所有模組了。

方法本身不複雜,但是這個Regex不簡單:

分析完deps之後,將模組定義存入緩衝:

注意,我們會發現define方法純粹只是分析模組、儲存模組,並沒有執行模組。

2、真正執行模組,是在require方法裡面。我們接下來看require。

簡而言之require方法就是根據id在define定義儲存的模組緩衝中找到相應的模組,並執行它,獲得模組定義返回的方法:

整個這個大步驟中,有一個很關鍵的步驟,有必要細說:

Module.get(require.resolve(id))。

require一個模組的時候,首先要找到這個模組。 Module.get方法就起這個作用。

cachedMods裡面沒有的話,就建立一個新的Module並緩衝到cachedMods裡面:

define和rquire方法這樣看來不算複雜。seajs主要還是模組載入的邏輯有點複雜。

3、seajs真正執行的入口,是use方法:

通過use方法,從這裡的ids開始觸發模組的載入和執行。

可以看到載入的關鍵點在mod.load方法。

load方法代碼有點長,其中的主要邏輯是:判斷mod的目前狀態是否為已載入或者載入中。

在Module的舒適化函數中,我們可以看到status預設值是0.

所以沒有載入過的新模組,到這裡都是: mod.status = STATUS.LOADING 狀態設定為載入中,並執行後續載入邏輯。

接來下是擷取模組的依賴urls

mod.resolve方法:

Module.resolve方法本質上就是把相對路徑、配置的path、別名等轉換成一個絕對路徑。不貼代碼了。

更新模組載入狀態。

載入模組的邏輯:

主要是m.fetch方法,裡面其他邏輯這裡略過。

可以看到 seajs.request最終會去執行模組檔案的載入:

當所有相依模組載入完了之後,執行mod的onload方法

這裡是 mod.onload()方法

到此,seajs的核心邏輯就差不多都看到了。供參考,有理解不到位或者表達不準確的地方,歡迎一起探討。

以上所述就是本文的全部內容了,希望大家能夠喜歡。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.