seajs 源碼解讀

來源:互聯網
上載者:User

標籤:style   blog   http   color   java   os   io   檔案   

之前面試時老問一個問題seajs 是怎麼載入js 檔案的

在網上找一些資料,覺得這個寫的不錯就轉載了,記錄一下,也學習一下

seajs 源碼解讀

seajs 簡單介紹

 

seajs是前端應用模組化開發的一種很好的解決方案。對於多人協作開發的、複雜龐大的前端項目尤其有用。簡單的介紹不多說,大家可以到seajs的官網seajs.org參看介紹。本文主要簡單地解讀一下seajs的源碼和模組化原理。如果有描述不實的地方,希望大家指正和交流。
註:本文的解析是基於seajs的2.2.1版本。

目錄結構

解壓seajs之後的src目錄結構如下:

intro.js             -- 全域閉包頭部sea.js               -- 基本命名空間util-lang.js         -- 語言增強util-events.js       -- 簡易事件機制util-path.js         -- 路徑處理util-request.js      -- HTTP 要求util-deps.js         -- 依賴提取module.js            -- 核心代碼config.js            -- 配置outro.js             -- 全域閉包尾部

src目錄存放主要的seajs原始碼。各個檔案的作用也如上面所示。其中,module.js是這次源碼解讀的核心,但我也會順帶介紹一下其他檔案的作用的。
sea.js對代碼比較簡單,其實就是聲明一下全域的seajs命名空間。
intro.js和outro.js則是我們熟悉的匿名函數包裹基本代碼的方式,只是這裡比較特別的是,這段匿名函數被拆分成intro.js和outro.js兩個檔案。這樣的做法主要是方便調試,在調試的環境下,不引用intro.js和outro.js即可以直接在全域裡暴露seajs內部的介面,調試起來比較方便。intro.js和outro.js合并起來的代碼如下:

(function(global, undefined) {    if (global.seajs) {      return    }    // ....})(this);

其他檔案的用途就不一一重複敘述了,看列表即可。

頁面如何動態載入js檔案

 

在解析seajs的源碼和原理之前,讓我們來回憶一下,在沒有seajs或者requirejs的情況下,最原始的動態指令碼載入方法是怎樣的。方法很簡單:其實就是建立一個script的標籤,設定了src為你想要載入的指令碼url,把script標籤append到Dom裡去就想了,so easy!沒錯,絕大部分模組載入js庫的原理都是如此。

var script = document.createElement(‘script‘);script.setAttribute(‘src‘, ‘example.js‘);script.onload = function() {    console.log("script loaded!");};document.body.appendChild(script);

上述代碼即可以完成一次簡單的動態指令碼載入。然而,seajs真正的核心在於處理模組依賴的問題。在前端JS開發領域,尤其是複雜的web應用,模組依賴問題一直是令人頭疼的問題。
很簡單的道理,例如A、B、C、D四個模組對應於A.js、B.js、C.js、D.js四個檔案。他們之間的依賴關係例如以下:

  • A 依賴 B
  • B 依賴 C和D

問題在於,如何找出模組裡的依賴關係,如何確保A在運行前已經載入了B等等。這些都是前端模組化和模組依賴需要解決的問題

模組化實現思路

seajs的模組化實現原理,說簡單其實不簡單,說複雜其實也不是很複雜。主要思路可以用下面這一段代碼來說明:

Module.define = function (id, deps, factory) {    // 擷取代碼中聲明的依賴關係    deps = parseDependencies(factory.toString());    // 儲存    Module.save();    // 匹配到url    var url = Module.resolve(id);    // 載入指令碼    script.url = url;    loadScript();    // 執行factory並儲存模組的引用    ...};

擷取代碼中聲明的依賴

首先我們來看看如何擷取代碼中聲明需要依賴的模組。一般情況下,seajs中同步載入模組的寫法是類似這樣的:

define(‘scripts/a‘, function(require, exports, module) {    var factory = function() {        var moduleB = require(‘scripts/b‘);        ...    };    module.exports = factory;});

那麼需要擷取依賴的資訊,我們可以藉助Function的toString方法,一個函數的toString方法是會返回函數本身的代碼的(對於JavaScript自身的函數,會返回[native code])。只需要Regex來匹配require關鍵詞後面的參考關聯性即可。所以seajs中函數parseDependencies的寫法就像這樣(這一部分代碼在util-deps.js):

var SLASH_RE = /\\\\/gvar REQUIRE_RE = /"(?:\\"|[^"])*"|‘(?:\\‘|[^‘])*‘|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["‘])(.+?)\1\s*\)/gfunction parseDependencies(code) {  var ret = []  code.replace(SLASH_RE, "")        // 匹配require關鍵詞,找出依賴關係      .replace(REQUIRE_RE, function(m, m1, m2) {        if (m2) {          ret.push(m2)        }      })  return ret}

通過id來匹配指令碼的url地址

然後找出代碼中聲明的依賴id,通過id來匹配正確的指令碼url地址。這一部分的代碼在util-path.js

function id2Uri(id, refUri) {  if (!id) return ""  id = parseAlias(id)  id = parsePaths(id)  id = parseVars(id)  id = normalize(id)  var uri = addBase(id, refUri)  uri = parseMap(uri)  return uri}

這裡有個特別的地方,類似require(‘a/b/c‘)這樣的寫法,seajs是如何知道指令碼地址的絕對路徑的呢?道理很簡單,就是通過seajs自己往dom裡添加的id為‘seajsnode‘的script節點或者是當前html中最後一個script節點,通過這些節點的src屬性擷取指令碼的絕對路徑。

模組載入過程

讓我們把目光移回到核心的module.js中。seajs為模組的載入流程定義了6種狀態。

var STATUS = Module.STATUS = {  // 1 - The `module.uri` is being fetched  FETCHING: 1,  // 2 - The meta data has been saved to cachedMods  SAVED: 2,  // 3 - The `module.dependencies` are being loaded  LOADING: 3,  // 4 - The module are ready to execute  LOADED: 4,  // 5 - The module is being executed  EXECUTING: 5,  // 6 - The `module.exports` is available  EXECUTED: 6}

也就是:
FETCHING 開始載入當前模組
SAVED 當前模組載入完成並儲存模組資料
LOADING 開始載入依賴的模組
LOADED 相依模組已經載入完成
EXECUTING 當前模組執行中
EXECUTED 當前模組執行完成

其實這一載入執行過程並非線性,當前模組在載入所依賴的模組的是,所依賴的模組同樣也需要進行這一過程,直到所有的依賴都載入執行完畢,當前模組才開始執行。

在module.js中seajs中的一些方法說明了上述整個流程。

  • Module.use 構造一個沒有factory的模組,開始整個載入流程,狀態初始化為FETCHING到SAVED;
  • Module.prototype.load 通過load方法,開始載入子模組,狀態由SAVED到LOADING;
  • Module.prototype.onload 當子模組都載入完成後都會調用onload方法,狀態由LOADING到LOADED;
  • Module.prototype.exec 載入過程都結束了,開始執行模組,狀態由EXECUTING到EXECUTED;

這裡每個方法的詳細過程就不一一解析,有興趣的同學可以去看源碼。
實際上,seajs會對載入過的模組儲存一份引用在cachedMods中,在require的時候會先調用緩衝中的模組。

seajs.require = function(id) {  var mod = Module.get(Module.resolve(id))  if (mod.status < STATUS.EXECUTING) {    mod.onload()    mod.exec()  }  return mod.exports}Module.get = function(uri, deps) {  return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))}

總結

前端模組化一直是前端開發中比較重要的一點。前端開發相對其他語言來說比較特殊,尤其是對應大型Web項目的前端代碼,如何簡潔優雅地劃分模組,如何管理這些模組的依賴問題,這些都需要花一定的時間去認識和探討。因此,Common.js(致力於設計、規劃並標準化 JavaScript API)的誕生開啟了“ JavaScript 模組化的時代”。前端領域的模組化方案,像requireJS、SeaJS等都是Common.js的實踐者,對我們規劃前端的代碼很有協助。然而,問題其實還有很多,seajs依然未能完全滿足前端模組化開發,在效能問題、打包部署等方法還有著不足,不過技術的未來總在進步,相信以後會有更好的解決方案。

轉自:http://blog.segmentfault.com/civerzhu/1190000000471722

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.