AMD加载器实现笔记(四)
繼續這一系列的內容,到目前為止除了AMD規范中config的map、config參數外,我們已經全部支持其他屬性了。這一篇文章中,我們來為增加對map的支持。同樣問題,想要增加map的支持首先要知道map的語義。
主要用于解決在兩個不同模塊集中使用一個模塊的不同版本,并且保證兩個模塊集的交互沒有沖突。
假設磁盤有如下文件:
當'some/newmodule'請求'foo'模塊時,它將從foo1.2.js總得到'foo1.2'模塊;當'some/oldmodule'請求'foo'模塊時它將從foo1.0中得到'foo1.0'模塊。
在map屬性中可以使用任何的module ID前綴,并且mapping對象可以匹配任何別的module ID前綴。
如果出現通配符‘*’,表示任何模塊使用這個匹配配置。通配符匹配對象中的模塊ID前綴可以被覆蓋。
?
通過上文的解釋,可以明白,如果在'some/newmodule'中依賴的foo實際是上依賴的foo1.2。轉化成代碼邏輯應當是這樣的:如果在‘some/module’模塊中發現依賴foo模塊那就將foo替換成foo1.2。但是在什么地方實現替換好呢?因為模塊的定義從define開始,同時只有在define中才能獲得模塊的絕對路徑,所以我們把替換的處理放在define中。那么問題來了,我們的模塊大部分都是匿名模塊,模塊自己如何知道自己的模塊Id?所以一定要有一個標記去告訴define函數當前模塊的Id,我們知道每一個模塊都是一個JavaScript文件,每一個模塊都有一個對應的script元素,所以最好的做法是沒每一個script都加一個自定義特性,來標記當前元素的模塊Id。
所以在loadJs中要為script加自定義特性:
在define函數中,通過文件的絕對路徑,找出對應的script元素,拿到模塊Id,判斷如果在map中,則進行替換:
function getMapSetting(mId) {if (mId in require.parsedConfig.map) {return require.parsedConfig[mId];} else if ('*' in require.parsedConfig.map) {return require.parsedConfig.map['*'];} else {return null;}};
目前為止,我們的加載器已經支持了map屬性,完整代碼如下:
(function(global){global = global || window;modules = {};loadings = [];loadedJs = [];//module: id, state, factory, result, deps;global.require = function(deps, callback, parent){var id = parent || "Bodhi" + Date.now();var cn = 0, dn = deps.length;var args = [];var oriDeps = deps.slice();//保留原始dep的模塊Id// dep為非絕對路徑形式,而modules的key仍然需要絕對路徑deps = deps.map(function(dep) {if (modules[dep]) { //jquery return dep;} else if (dep in global.require.parsedConfig.paths) {return dep;}var rel = "";if (/^Bodhi/.test(id)) {rel = global.require.parsedConfig.baseUrl;} else {var parts = parent.split('/');parts.pop();rel = parts.join('/');}return getModuleUrl(dep, rel);});var module = {id: id,deps: deps,factory: callback,state: 1,result: null};modules[id] = module;if (checkCircleRef(id, id)) {return;}deps.forEach(function(dep, i) {if (modules[dep] && modules[dep].state === 2) {cn++args.push(modules[dep].result);} else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {loadJS(dep, oriDeps[i]);loadedJs.push(dep);}});if (cn === dn) {callFactory(module);} else {loadings.push(id);checkDeps();}};global.require.config = function(config) {this.parsedConfig = {};if (config.baseUrl) {var currentUrl = getCurrentScript();var parts = currentUrl.split('/');parts.pop();var currentDir = parts.join('/');this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);}var burl = this.parsedConfig.baseUrl;// 得到baseUrl后,location相對baseUrl定位this.parsedConfig.packages = [];if (config.packages) {for (var i = 0, len = config.packages.length; i < len; i++) {var pck = config.packages[i];var cp = {name: pck.name,location: getRoute(burl, pck.location)}this.parsedConfig.packages.push(cp);}}this.parsedConfig.paths = {};if (config.paths) {for (var p in config.paths) {this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);}}this.parsedConfig.map = {};if (config.map) {this.parsedConfig.map = config.map;}this.parsedConfig.shim = {};//shim 要放在最后處理if (config.shim) {this.parsedConfig.shim = config.shim;for (var p in config.shim) {var item = config.shim[p];define(p, item.deps, function() {var exports;if (item.init) {exports = item.init.apply(item, arguments);}return exports ? exports : item.exports;});}}console.log(this.parsedConfig);}global.define = function(id, deps, callback) {//加上moduleId的支持if (typeof id !== "string" && arguments.length === 2) {callback = deps;deps = id;id = "";}var id = id || getCurrentScript();var script = document.querySelector('script[src="' + id + '"]');if (script || id in require.parsedConfig.shim) {var mId = script ? script.getAttribute('data-moduleId') : id;var maping = getMapSetting(mId);if (maping) {deps = deps.map(function(dep) {return maping[dep] || dep;});}}if (modules[id]) {console.error('multiple define module: ' + id);}require(deps, callback, id);};global.define.amd = {};//AMD規范function getMapSetting(mId) {if (mId in require.parsedConfig.map) {return require.parsedConfig[mId];} else if ('*' in require.parsedConfig.map) {return require.parsedConfig.map['*'];} else {return null;}};function checkCircleRef(start, target){var m = modules[start];if (!m) {return false;}var depModules = m.deps.map(function(dep) {return modules[dep] || null;});return depModules.some(function(m) {if (!m) {return false;}return m.deps.some(function(dep) {var equal = dep === target;if (equal) {console.error("circle reference: ", target, m.id);}return equal;});}) ? true : depModules.some(function(m) {if (!m) {return false;}return m.deps.some(function(dep) {return checkCircleRef(dep, target);});});//return hasCr ? true: };function getRoute(base, target) {var bts = base.replace(/\/$/, "").split('/'); //base dirvar tts = target.split('/'); //target partswhile (isDefined(tts[0])) {if (tts[0] === '.') {return bts.join('/') + '/' + tts.slice(1).join('/');} else if (tts[0] === '..') {bts.pop();tts.shift();} else {return bts.join('/') + '/' + tts.join('/');}}};function isDefined(v) {return v !== null && v !== undefined;};function getModuleUrl(moduleId, relative) {function getPackage(nm) {for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {var pck = require.parsedConfig.packages[i];if (nm === pck.name) {return pck;}}return false;}var mts = moduleId.split('/');var pck = getPackage(mts[0]);if (pck) {mts.shift();return getRoute(pck.location, mts.join('/'));} else if (mts[0] === '.' || mts[0] === '..') {return getRoute(relative, moduleId);} else {return getRoute(require.parsedConfig.baseUrl, moduleId);}};function loadJS(url, mId) {var script = document.createElement('script');script.setAttribute('data-moduleId', mId); //為script元素保留原始模塊Idscript.type = "text/javascript";//判斷模塊是否在paths中定義了路徑script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + '.js';script.onload = function() {var module = modules[url];if (module && isReady(module) && loadings.indexOf(url) > -1) {callFactory(module);}checkDeps();};var head = document.getElementsByTagName('head')[0];head.appendChild(script);};function checkDeps() {for (var p in modules) {var module = modules[p];if (isReady(module) && loadings.indexOf(module.id) > -1) {callFactory(module);checkDeps(); // 如果成功,在執行一次,防止有些模塊就差這次模塊沒有成功}}};function isReady(m) {var deps = m.deps;var allReady = deps.every(function(dep) {return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;})if (deps.length === 0 || allReady) {return true;}};function callFactory(m) {var args = [];for (var i = 0, len = m.deps.length; i < len; i++) {args.push(modules[m.deps[i]].result);}m.result = m.factory.apply(window, args);m.state = 2;var idx = loadings.indexOf(m.id);if (idx > -1) {loadings.splice(idx, 1);}};function getCurrentScript(base) {// 參考 https://github.com/samyk/jiagra/blob/master/jiagra.jsvar stack;try {a.b.c(); //強制報錯,以便捕獲e.stack} catch (e) { //safari的錯誤對象只有line,sourceId,sourceURLstack = e.stack;if (!stack && window.opera) {//opera 9沒有e.stack,但有e.Backtrace,但不能直接取得,需要對e對象轉字符串進行抽取stack = (String(e).match(/of linked script \S+/g) || []).join(" ");}}if (stack) {/**e.stack最后一行在所有支持的瀏覽器大致如下:*chrome23:* at http://113.93.50.63/data.js:4:1*firefox17:*@http://113.93.50.63/query.js:4*opera12:http://www.oldapps.com/opera.php?system=Windows_XP*@http://113.93.50.63/data.js:4*IE10:* at Global code (http://113.93.50.63/data.js:4:1)* //firefox4+ 可以用document.currentScript*/stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一個空格或@之后的部分stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, ""); //去掉換行符return stack.replace(/(:\d+)?:\d+$/i, "").replace(/\.js$/, ""); //去掉行號與或許存在的出錯字符起始位置}var nodes = (base ? document : head).getElementsByTagName("script"); //只在head標簽中尋找for (var i = nodes.length, node; node = nodes[--i]; ) {if ((base || node.className === moduleClass) && node.readyState === "interactive") {return node.className = node.src;}}}; })(window)
下面我們看一個demo:
使用我們的加載器來加載jquery,同時禁用jquery的全局模式$:
jquery-private代碼如下:
如果某一模塊依賴jquery,那么將會加載jquery-private。而在jquery中,因為配置了map和paths,所以jquery-private中的jquery根據paths找到jquery文件,并加載。同時在jquery-private中將禁用了全局模式之后的jquery對象返回給something模塊。通過這個配置所有的模塊在引用jquery時,實際上是引用了jquery-private模塊。
總結
以上是生活随笔為你收集整理的AMD加载器实现笔记(四)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工作分解结构图(Work Breakdo
- 下一篇: 高效编程之互斥锁和自旋锁