亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

Seajs源碼詳解分析

2019-11-19 11:54:19
字體:
來源:轉載
供稿:網友

近幾年前端工程化越來越完善,打包工具也已經是前端標配了,像seajs這種老古董早已停止維護,而且使用的人估計也幾個了。但這并不能阻止好奇的我,為了了解當年的前端前輩們是如何在瀏覽器進行代碼模塊化的,我鼓起勇氣翻開了Seajs的源碼。下面就和我一起細細品味Seajs源碼吧。

如何使用seajs

在看Seajs源碼之前,先看看Seajs是如何使用的,畢竟剛入行的時候,大家就都使用browserify、webpack之類的東西了,還從來沒有用過Seajs。

<!-- 首先在頁面中引入sea.js,也可以使用CDN資源 --><script type="text/javascript" src="./sea.js"></script><script>// 設置一些參數seajs.config({ debug: true, // debug為false時,在模塊加載完畢后會移除head中的script標簽 base: './js/', // 通過路徑加載其他模塊的默認根目錄 alias: { // 別名 jquery: 'https://cdn.bootcss.com/jquery/3.2.1/jquery' }})seajs.use('main', function(main) { alert(main)})</script>//main.jsdefine(function (require, exports, module) { // require('jquery') // var $ = window.$ module.exports = 'main-module'})

seajs的參數配置

首先通過script導入seajs,然后對seajs進行一些配置。seajs的配置參數很多具體不詳細介紹,seajs將配置項會存入一個私有對象data中,并且如果之前有設置過某個屬性,并且這個屬性是數組或者對象,會將新值與舊值進行合并。

(function (global, undefined) { if (global.seajs) { return } var data = seajs.data = {}  seajs.config = function (configData) { for (var key in configData) {  var curr = configData[key] // 獲取當前配置  var prev = data[key] // 獲取之前的配置  if (prev && isObject(prev)) { // 如果之前已經設置過,且為一個對象  for (var k in curr) {   prev[k] = curr[k] // 用新值覆蓋舊值,舊值保留不變  }  }  else {  // 如果之前的值為數組,進行concat  if (isArray(prev)) {   curr = prev.concat(curr)  }  // 確保 base 為一個路徑  else if (key === "base") {   // 必須已 "/" 結尾   if (curr.slice(-1) !== "/") {   curr += "/"   }   curr = addBase(curr) // 轉換為絕對路徑  }  // Set config  data[key] = curr   } } }})(this);

設置的時候還有個比較特殊的地方,就是base這個屬性。這表示所有模塊加載的基礎路徑,所以格式必須為一個路徑,并且該路徑最后會轉換為絕對路徑。比如,我的配置為base: './js',我當前訪問的域名為http://qq.com/web/index.html,最后base屬性會被轉化為http://qq.com/web/js/。然后,所有依賴的模塊id都會根據該路徑轉換為uri,除非有定義其他配置,關于配置點到為止,到用到的地方再來細說。

模塊的加載與執行

下面我們調用了use方法,該方法就是用來加載模塊的地方,類似與requirejs中的require方法。

// requirejsrequire(['main'], function (main) { console.log(main)});

只是這里的依賴項,seajs可以傳入字符串,而requirejs必須為一個數組,seajs會將字符串轉為數組,在內部seajs.use會直接調用Module.use。這個Module為一個構造函數,里面掛載了所有與模塊加載相關的方法,還有很多靜態方法,比如實例化Module、轉換模塊id為uri、定義模塊等等,廢話不多說直接看代碼。

seajs.use = function(ids, callback) { Module.use(ids, callback, data.cwd + "_use_" + cid()) return seajs}// 該方法用來加載一個匿名模塊Module.use = function (ids, callback, uri) { //如果是通過seajs.use調用,uri是自動生成的 var mod = Module.get( uri, isArray(ids) ? ids : [ids] // 這里會將依賴模塊轉成數組 ) mod._entry.push(mod) // 表示當前模塊的入口為本身,后面還會把這個值傳入他的依賴模塊 mod.history = {} mod.remain = 1 // 這個值后面會用來標識依賴模塊是否已經全部加載完畢 mod.callback = function() { //設置模塊加載完畢的回調,這一部分很重要,尤其是exec方法 var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) {  exports[i] = cachedMods[uris[i]].exec() } if (callback) {  callback.apply(global, exports) //執行回調 } } mod.load()}

這個use方法一共做了三件事:

1.調用Module.get,進行Module實例化
2.為模塊綁定回調函數
3.調用load,進行依賴模塊的加載

實例化模塊,一切的開端

首先use方法調用了get靜態方法,這個方法是對Module進行實例化,并且將實例化的對象存入到全局對象cachedMods中進行緩存,并且以uri作為模塊的標識,如果之后有其他模塊加載該模塊就能直接在緩存中獲取。

var cachedMods = seajs.cache = {} // 模塊的緩存對象Module.get = function(uri, deps) { return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))}function Module(uri, deps) { this.uri = uri this.dependencies = deps || [] this.deps = {} // Ref the dependence modules this.status = 0 this._entry = []}

綁定的回調函數會在所有模塊加載完畢之后調用,我們先跳過,直接看load方法。load方法會先把所有依賴的模塊id轉為uri,然后進行實例化,最后調用fetch方法,綁定模塊加載成功或失敗的回調,最后進行模塊加載。具體代碼如下(代碼經過精簡)

// 所有依賴加載完畢后執行 onloadModule.prototype.load = function() { var mod = this mod.status = STATUS.LOADING // 狀態置為模塊加載中  // 調用resolve方法,將模塊id轉為uri。 // 比如之前的"mian",會在前面加上我們之前設置的base,然后在后面拼上js后綴 // 最后變成: "http://qq.com/web/js/main.js" var uris = mod.resolve() // 遍歷所有依賴項的uri,然后進行依賴模塊的實例化 for (var i = 0, len = uris.length; i < len; i++) { mod.deps[mod.dependencies[i]] = Module.get(uris[i]) } // 將entry傳入到所有的依賴模塊,這個entry是我們在use方法的時候設置的 mod.pass()  if (mod._entry.length) { mod.onload() return } // 開始進行并行加載 var requestCache = {} var m for (i = 0; i < len; i++) { m = cachedMods[uris[i]] // 獲取之前實例化的模塊對象 m.fetch(requestCache) // 進行fetch } // 發送請求進行模塊的加載 for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) {  requestCache[requestUri]() //調用 seajs.request } }}

將模塊id轉為uri

resolve方法實現可以稍微看下,基本上是把config里面的參數拿出來,進行拼接uri的處理。

Module.prototype.resolve = function() { var mod = this var ids = mod.dependencies // 取出所有依賴模塊的id var uris = [] // 進行遍歷操作 for (var i = 0, len = ids.length; i < len; i++) { uris[i] = Module.resolve(ids[i], mod.uri) //將模塊id轉為uri } return uris}Module.resolve = function(id, refUri) { var emitData = { id: id, refUri: refUri } return seajs.resolve(emitData.id, refUri) // 調用 id2Uri}seajs.resolve = id2Urifunction id2Uri(id, refUri) { // 將id轉為uri,轉換配置中的一些變量 if (!id) return "" id = parseAlias(id) id = parsePaths(id) id = parseAlias(id) id = parseVars(id) id = parseAlias(id) id = normalize(id) id = parseAlias(id) var uri = addBase(id, refUri) uri = parseAlias(uri) uri = parseMap(uri) return uri}

最后就是調用了id2Uri,將id轉為uri,其中調用了很多的parse方法,這些方法不一一去看,原理大致一樣,主要看下parseAlias。如果這個id有定義過alias,將alias取出,比如id為"jquery",之前在定義alias中又有定義jquery: 'https://cdn.bootcss.com/jquery/3.2.1/jquery',則將id轉化為'https://cdn.bootcss.com/jquery/3.2.1/jquery'。代碼如下:

function parseAlias(id) { //如果有定義alias,將id替換為別名對應的地址 var alias = data.alias return alias && isString(alias[id]) ? alias[id] : id}

為依賴添加入口,方便追根溯源

resolve之后獲得uri,通過uri進行Module的實例化,然后調用pass方法,這個方法主要是記錄入口模塊到底有多少個未加載的依賴項,存入到remain中,并將entry都存入到依賴模塊的_entry屬性中,方便回溯。而這個remain用于計數,最后onload的模塊數與remain相等就激活entry模塊的回調。具體代碼如下(代碼經過精簡)

Module.prototype.pass = function() { var mod = this var len = mod.dependencies.length // 遍歷入口模塊的_entry屬性,這個屬性一般只有一個值,就是它本身 // 具體可以回去看use方法 -> mod._entry.push(mod) for (var i = 0; i < mod._entry.length; i++) { var entry = mod._entry[i] // 獲取入口模塊 var count = 0 // 計數器,用于統計未進行加載的模塊 for (var j = 0; j < len; j++) {  var m = mod.deps[mod.dependencies[j]] //取出依賴的模塊  // 如果模塊未加載,并且在entry中未使用,將entry傳遞給依賴  if (m.status < STATUS.LOADED && !entry.history.hasOwnProperty(m.uri)) {  entry.history[m.uri] = true // 在入口模塊標識曾經加載過該依賴模塊  count++  m._entry.push(entry) // 將入口模塊存入依賴模塊的_entry屬性  } } // 如果未加載的依賴模塊大于0 if (count > 0) {  // 這里`count - 1`的原因也可以回去看use方法 -> mod.remain = 1  // remain的初始值就是1,表示默認就會有一個未加載的模塊,所有需要減1  entry.remain += count - 1  // 如果有未加載的依賴項,則移除掉入口模塊的entry  mod._entry.shift()  i-- } }}

如何發起請求,下載其他依賴模塊?

總的來說pass方法就是記錄了remain的數值,接下來就是重頭戲了,調用所有依賴項的fetch方法,然后進行依賴模塊的加載。調用fetch方法的時候會傳入一個requestCache對象,該對象用來緩存所有依賴模塊的request方法。

var requestCache = {}for (i = 0; i < len; i++) { m = cachedMods[uris[i]] // 獲取之前實例化的模塊對象 m.fetch(requestCache) // 進行fetch}Module.prototype.fetch = function(requestCache) { var mod = this var uri = mod.uri mod.status = STATUS.FETCHING callbackList[requestUri] = [mod] emit("request", emitData = { // 設置加載script時的一些數據 uri: uri, requestUri: requestUri, onRequest: onRequest, charset: isFunction(data.charset) ? data.charset(requestUri) : data.charset, crossorigin: isFunction(data.crossorigin) ? data.crossorigin(requestUri) : data.crossorigin }) if (!emitData.requested) { //發送請求加載js文件 requestCache[emitData.requestUri] = sendRequest } function sendRequest() { // 被request方法,最終會調用 seajs.request seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset, emitData.crossorigin) } function onRequest(error) { //模塊加載完畢的回調 var m, mods = callbackList[requestUri] delete callbackList[requestUri] // 保存元數據到匿名模塊,uri為請求js的uri if (anonymousMeta) {  Module.save(uri, anonymousMeta)  anonymousMeta = null } while ((m = mods.shift())) {  // When 404 occurs, the params error will be true  if(error === true) {  m.error()  }  else {  m.load()  } } }}

經過fetch操作后,能夠得到一個requestCache對象,該對象緩存了模塊的加載方法,從上面代碼就能看到,該方法最后調用的是seajs.request方法,并且傳入了一個onRequest回調。

for (var requestUri in requestCache) { requestCache[requestUri]() //調用 seajs.request}//用來加載js腳本的方法seajs.request = requestfunction request(url, callback, charset, crossorigin) { var node = doc.createElement("script") addOnload(node, callback, url) node.async = true //異步加載 node.src = url head.appendChild(node)}function addOnload(node, callback, url) { node.onload = onload node.onerror = function() { emit("error", { uri: url, node: node }) onload(true) } function onload(error) { node.onload = node.onerror = node.onreadystatechange = null // 腳本加載完畢的回調 callback(error) }}

通知入口模塊

上面就是request的邏輯,只不過刪除了一些兼容代碼,其實原理很簡單,和requirejs一樣,都是創建script標簽,綁定onload事件,然后插入head中。在onload事件發生時,會調用之前fetch定義的onRequest方法,該方法最后會調用load方法。沒錯這個load方法又出現了,那么依賴模塊調用和入口模塊調用有什么區別呢,主要體現在下面代碼中:

if (mod._entry.length) { mod.onload() return}

如果這個依賴模塊沒有另外的依賴模塊,那么他的entry就會存在,然后調用onload模塊,但是如果這個代碼中有define方法,并且還有其他依賴項,就會走上面那么邏輯,遍歷依賴項,轉換uri,調用fetch巴拉巴拉。這個后面再看,先看看onload會做什么。

Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED  for (var i = 0, len = (mod._entry || []).length; i < len; i++) { var entry = mod._entry[i] // 每次加載完畢一個依賴模塊,remain就-1 // 直到remain為0,就表示所有依賴模塊加載完畢 if (--entry.remain === 0) {  // 最后就會調用entry的callback方法  // 這就是前面為什么要給每個依賴模塊存入entry  entry.callback() } } delete mod._entry}

依賴模塊執行,完成全部操作

還記得最開始use方法中給入口模塊設置callback方法嗎,沒錯,兜兜轉轉我們又回到了起點。

mod.callback = function() { //設置模塊加載完畢的回調 var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { // 執行所有依賴模塊的exec方法,存入exports數組 exports[i] = cachedMods[uris[i]].exec() } if (callback) { callback.apply(global, exports) //執行回調 } // 移除一些屬性 delete mod.callback delete mod.history delete mod.remain delete mod._entry}

那么這個exec到底做了什么呢?

Module.prototype.exec = function () { var mod = this mod.status = STATUS.EXECUTING if (mod._entry && !mod._entry.length) { delete mod._entry } function require(id) { var m = mod.deps[id] return m.exec() } var factory = mod.factory // 調用define定義的回調 // 傳入commonjs相關三個參數: require, module.exports, module var exports = factory.call(mod.exports = {}, require, mod.exports, mod) if (exports === undefined) { exports = mod.exports //如果函數沒有返回值,就取mod.exports } mod.exports = exports mod.status = STATUS.EXECUTED return mod.exports // 返回模塊的exports}

這里的factory就是依賴模塊define中定義的回調函數,例如我們加載的main.js中,定義了一個模塊。

define(function (require, exports, module) { module.exports = 'main-module'})

那么調用這個factory的時候,exports就為module.exports,也是是字符串"main-moudle"。最后callback傳入的參數就是"main-moudle"。所以我們執行最開頭寫的那段代碼,最后會在頁面上彈出main-moudle。

define定義模塊

你以為到這里就結束了嗎?并沒有。前面只說了加載依賴模塊中define方法中沒有其他依賴,那如果有其他依賴呢?廢話不多說,先看看define方法做了什么:

global.define = Module.defineModule.define = function (id, deps, factory) { var argsLen = arguments.length // 參數校準 if (argsLen === 1) { factory = id id = undefined } else if (argsLen === 2) { factory = deps if (isArray(id)) {  deps = id  id = undefined } else {  deps = undefined } } // 如果沒有直接傳入依賴數組 // 則從factory中提取所有的依賴模塊到dep數組中 if (!isArray(deps) && isFunction(factory)) { deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString()) } var meta = { //模塊加載與定義的元數據 id: id, uri: Module.resolve(id), deps: deps, factory: factory } // 激活define事件, used in nocache plugin, seajs node version etc emit("define", meta) meta.uri ? Module.save(meta.uri, meta) : // 在腳本加載完畢的onload事件進行save anonymousMeta = meta }

首先進行了參數的修正,這個邏輯很簡單,直接跳過。第二步判斷了有沒有依賴數組,如果沒有,就通過parseDependencies方法從factory中獲取。這個方法很有意思,是一個狀態機,會一步步的去解析字符串,匹配到require,將其中的模塊取出,最后放到一個數組里。這個方法在requirejs中是通過正則實現的,早期seajs也是通過正則匹配的,后來改成了這種狀態機的方式,可能是考慮到性能的問題。seajs的倉庫中專門有一個模塊來講這個東西的,請看鏈接。

獲取到依賴模塊之后又設置了一個meta對象,這個就表示這個模塊的原數據,里面有記錄模塊的依賴項、id、factory等。如果這個模塊define的時候沒有設置id,就表示是個匿名模塊,那怎么才能與之前發起請求的那個mod相匹配呢?

這里就有了一個全局變量anonymousMeta,先將元數據放入這個對象。然后回過頭看看模塊加載時設置的onload函數里面有一段就是獲取這個全局變量的。

function onRequest(error) { //模塊加載完畢的回調... // 保存元數據到匿名模塊,uri為請求js的uri if (anonymousMeta) { Module.save(uri, anonymousMeta) anonymousMeta = null }...}

不管是不是匿名模塊,最后都是通過save方法,將元數據存入到mod中。

// 存儲元數據到 cachedMods 中Module.save = function(uri, meta) { var mod = Module.get(uri)  if (mod.status < STATUS.SAVED) { mod.id = meta.id || uri mod.dependencies = meta.deps || [] mod.factory = meta.factory mod.status = STATUS.SAVED }}

這里完成之后,就是和前面的邏輯一樣了,先去校驗當前模塊有沒有依賴項,如果有依賴項,就去加載依賴項和use的邏輯是一樣的,等依賴項全部加載完畢后,通知入口模塊的remain減1,知道remain為0,最后調用入口模塊的回調方法。整個seajs的邏輯就已經全部走通,Yeah!

結語

有過看requirejs的經驗,再來看seajs還是順暢很多,對模塊化的理解有了更加深刻的理解。閱讀源碼之前還是得對框架有個基本認識,并且有使用過,要不然很多地方都很懵懂。所以以后還是閱讀一些工作中有經常使用的框架或類庫的源碼進行閱讀,不能總像個無頭蒼蠅一樣。

最后用一張流程圖,總結下seajs的加載過程。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
2018中文字幕一区二区三区| 亚洲一区二区三区香蕉| 精品国产老师黑色丝袜高跟鞋| 国产香蕉一区二区三区在线视频| 中文字幕亚洲色图| 欧美限制级电影在线观看| 日韩美女免费线视频| 欧洲亚洲免费在线| 狠狠综合久久av一区二区小说| 国产一区二区三区18| 欧美韩国理论所午夜片917电影| 亚洲成人黄色在线观看| 中文字幕亚洲一区| 亚洲日韩欧美视频一区| 自拍偷拍免费精品| 国产精品美女久久久久久免费| 91精品国产91久久久久| 亚洲欧美999| 中文字幕日韩专区| 最近2019中文免费高清视频观看www99| 欧美日韩国产麻豆| 最近2019中文免费高清视频观看www99| 日韩成人中文字幕在线观看| 欧美做受高潮电影o| 欧美亚洲激情在线| 国内精品久久久久久| 久久久之久亚州精品露出| 欧美激情极品视频| 亚洲区免费影片| 日韩中文字在线| 亚洲美女精品久久| 91精品国产高清久久久久久久久| 欧美日韩福利在线观看| 色婷婷av一区二区三区久久| 亚洲精品自在久久| 欧美乱妇40p| 精品亚洲永久免费精品| 亚洲精品成a人在线观看| 成人字幕网zmw| 日韩在线中文视频| 国产一区二区三区三区在线观看| 秋霞成人午夜鲁丝一区二区三区| 午夜精品一区二区三区视频免费看| 欧美精品videossex88| www.日本久久久久com.| 91国产美女视频| 欧洲成人免费aa| 国产精品久久久久久久久借妻| 久久久这里只有精品视频| 国产日韩欧美在线| 美女av一区二区三区| 精品视频在线播放色网色视频| 国产精品白丝av嫩草影院| 欧美电影《睫毛膏》| 久久影视三级福利片| 欧美激情高清视频| 色伦专区97中文字幕| 久久久久久国产精品| 亚洲欧美成人精品| 91av在线免费观看视频| 国产美女精品视频免费观看| 国产主播在线一区| 欧美大片在线看免费观看| 久久精品这里热有精品| 国产+人+亚洲| www.日本久久久久com.| 国产视频精品va久久久久久| 国产成人精品日本亚洲| 日韩免费黄色av| 琪琪亚洲精品午夜在线| 日韩免费高清在线观看| 疯狂欧美牲乱大交777| 欧美精品videosex牲欧美| 亚洲精品日韩久久久| 国内精品视频在线| 久久免费视频在线| 成人xxxxx| 国产主播精品在线| 欧美高清在线播放| 久久精品国产综合| 欧美裸体xxxx极品少妇| 国产97在线|亚洲| 精品福利视频导航| 成人激情电影一区二区| 午夜精品久久久久久久男人的天堂| 欧美大片免费观看在线观看网站推荐| 亚洲欧美激情一区| 国产98色在线| 亚洲在线免费视频| 欧美激情xxxx| 欧美激情在线狂野欧美精品| 欧美在线视频导航| 久久视频在线视频| 亚洲美女性生活视频| 国产精品成av人在线视午夜片| 国产精品久久婷婷六月丁香| 国产精品久久久久久久久久ktv| 日韩在线观看免费全集电视剧网站| 国产成人精品久久| 日韩中文字幕精品| 亚洲开心激情网| 亚洲欧美日韩综合| 26uuu日韩精品一区二区| 动漫精品一区二区| 国产一区二区欧美日韩| 国产精品揄拍500视频| 国产成人精品视| 国产一区二区三区直播精品电影| 国产精品久久久久久久久久| 久久av红桃一区二区小说| 欧美综合第一页| 亚洲电影免费观看| 亚洲福利视频免费观看| 精品视频在线导航| 国产成人jvid在线播放| 亚洲精品国精品久久99热一| 亚洲性日韩精品一区二区| 中文字幕一区日韩电影| 九九热精品视频| 亚洲国产99精品国自产| 日韩亚洲欧美中文高清在线| 国产精品欧美激情在线播放| 亚洲精品日产aⅴ| 亚洲国产精品热久久| 欧美激情a∨在线视频播放| 中文.日本.精品| 亚洲天堂男人天堂女人天堂| 精品视频偷偷看在线观看| 51色欧美片视频在线观看| 97久久国产精品| 亚洲在线一区二区| 夜夜躁日日躁狠狠久久88av| 亚洲摸下面视频| 性色av一区二区三区红粉影视| 亚洲福利视频在线| 国产精品视频yy9099| 久久精品成人一区二区三区| 国产91精品青草社区| 亚洲人成电影网站色xx| 久久人人爽人人爽人人片av高清| 欧美日韩亚洲视频| 日韩欧美成人免费视频| 国内精品久久久久伊人av| 色七七影院综合| 成人久久18免费网站图片| 91免费精品国偷自产在线| 国产91网红主播在线观看| 国产网站欧美日韩免费精品在线观看| 精品国产一区二区三区在线观看| 欧美日韩一区二区三区| 欧美一级淫片丝袜脚交| 国产精品mp4| 不卡av在线网站| 欧美精品情趣视频| 亚洲xxxx3d| 欧美与欧洲交xxxx免费观看| 亚洲综合中文字幕在线观看| 亚洲精品久久久久中文字幕二区| 一道本无吗dⅴd在线播放一区| 久久久久久九九九| 久久好看免费视频| 欧美做爰性生交视频| 91久久久久久国产精品| 精品香蕉一区二区三区|