23年,我又学习了一次amd模块化,模块化思想
项目目录
src
view1
index.html
main.js
view2
plugins
module.js
jquery.js
......
modules // amd模块文件
a1.js
b1.js
c.js
b2.js
b21.js
src/view1/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 引入amd插件--> <!-- amd都有一个入口文件,我这里设计的方式是:自动引入同级目录下的main.js--> <script src="/plugins/module.js"></script> </body> </html>
src/view1/main.js
module .configs({ baseUrl: '../modules' }) .require(['a1','a2'], function(a2) { console.log('a2: ', a2) })
plugins/module.js
// 使用IIFE (function(win){ function module(){ this.context = { baseUrl: './', alias: {}, entry: 'main.js', suffix: '.js', } this.deps = {}; this.srcs = []; this.customEvt = new module.CustomEvt(); this.ready(); } var utils = module.utils = { each(arr, callback){ var keys = Object.keys(arr); for(var i = 0; i < keys.length; i++) { if(callback.call(arr, arr[i], i) === false) { break; } } }, // 只保留指定的keys inKeys(target, keys) { return Object.keys(target).reduce((newData, key) => { if (keys.includes(key)) { newData[key] = target[key]; } return newData; }, {}); }, // 路径拼接 pathJoin(...paths){ let path = ''; for(let i = paths.length - 1; i > -1; i--) { let pathItem = paths[i].replace(/\/+$/, ''); // 如果是绝对路径 if(/^\//.test(pathItem)) { path = pathItem + '/'+ path; break; } // 如果是相对路径 if(/^\.\//.test(pathItem)) { pathItem = pathItem.slice(2) } // 如果是: ../../a/b/c if(/^(\.{2}\/)+\S+/.test(pathItem) && paths[i-1]) { let matches = null; while(matches = /^\.{2}\/(\S+)/.exec(pathItem)) { pathItem = matches[1]; let prevPath = paths[i-1].replace(/\/+$/, ''); if(prevPath) { paths[i-1] = prevPath.slice(0, prevPath.lastIndexOf('/')) } } } path = pathItem + '/'+ path; } return path.replace(/\/+$/, ''); }, createScript(src) { var node = document.createElement('script'); node.type = 'text/javascript'; node.async = true; node.src = src; return node; }, // 判断所有依赖是否加载完成 isDepsDone(options) { var { depsTree, entry = depsTree.entry, deps } = options; var deps = deps || depsTree[entry].deps; var done = true; utils.each(deps, function(moduleName) { var dep = depsTree[moduleName]; if(dep) { if(dep.deps.length > 0) { done = utils.isDepsDone({ depsTree, entry, deps: dep.deps }); if(!done) { return false; } } }else { done = false; return false; } }); return done; } } var CustomEvt = function(){ this.subEvents = {}; }; CustomEvt.prototype = { constructor:CustomEvt ,$emit:function(event,data){ event = this.subEvents[event]; if(event){ for(let i = 0; i < event.length; i++){ event[i].apply(this, data); } } return this; } ,$on:function(event,handle){ let subEvents = this.subEvents; !(event in subEvents) && (subEvents[event] = []) subEvents[event].push(handle); return this; } ,$off:function(event,handle){ let events = this.subEvents[event]; if(!handle){ events && Reflect.deleteProperty(this.subEvents, event); }else{ if(typeof handle == 'function' && events){ for(let i = 0,len = events.length; i < len; i++){ let event = events[i]; if(event == handle){ return events.splice(i,1); } } } } return this; } } module.CustomEvt = CustomEvt; // // 主题对象 // function Subject() { // this.observers = []; // } // // 添加观察者 // Subject.prototype.addObserver = function(observer) { // !this.observers.includes(observer) && this.addObserver.push(observer); // } // Subject.prototype.notify = function(message) { // this.observers.forEach(observer => { // observer.update(message);//观察者接收消息的方法 // }) // } // function Observer() {} // Observer.prototype.update = function(message) { // } Object.assign( module.prototype, { // 入口函数 ready() { this.setCtxProxy(); // 这里默认加载同级目录下的main.js,请确保main.js的存在 var script = utils.createScript(this.getUrl('./', this.entry)); document.body.appendChild(script); }, // 设置context对象的属性,通过代理访问 setCtxProxy(){ var _self = this; var ctx = this.context; utils.each(Object.keys(ctx), function(key) { Object.defineProperty(_self, key, { get: function() { return ctx[key]; }, set: function(value) { if(ctx[key] !== value) { ctx[key] = value; return true; } } }) }); }, configs(options) { Object.assign(this.context, utils.inKeys(options, ['baseUrl', 'alias', 'entry'])) return this; }, getUrl(baseUrl, moduleName) { var path = utils.pathJoin(baseUrl, moduleName); return new RegExp('\\'+this.suffix+'$').test(path) ? path : path + this.suffix; }, // 核心方法:define,收集依赖 define(moduleName, deps, factory) { typeof deps == 'function' && ( factory = deps, deps = [] ) if(!this.deps[moduleName]) { this.deps[moduleName] = { moduleName, deps, factory }; } }, // 核心方法 // 迭代收集每一个入口依赖 traverseDeps(deps, callback){ var _self = this; function buildDepsRelation(moduleName, depsTree) { var oldDepsTree = depsTree; var module = _self.deps[moduleName]; depsTree = depsTree || { entry: moduleName }; depsTree[moduleName] = module; if(module.deps.length > 0) { traverseScript(module.deps, depsTree); }else { // 所有依赖收集完,才返回 if(utils.isDepsDone({ depsTree })) { typeof callback == 'function' && callback(depsTree); } } // 表示是第一层的递归,只重置第一层的递归 if(!oldDepsTree) { depsTree = null; } } function traverseScript(deps, depsTree) { utils.each(deps, function(moduleName, i) { var curSrc = _self.getUrl(_self.baseUrl, moduleName); var isExistSrc = _self.srcs.includes(curSrc); // 判断相同的依赖是否已经加载过 if(!isExistSrc) { _self.srcs.push(curSrc); var script = utils.createScript(curSrc); script.onload = function() { _self.customEvt.$emit('scriptLoaded', [moduleName]) buildDepsRelation(moduleName, depsTree); } document.body.appendChild(script); }else { let curModuleName = moduleName; // 1,依赖已加载完成 if(_self.deps[curModuleName]) { buildDepsRelation(moduleName, depsTree); }else { // 2,scriptLoad加载完成后,this.deps才有值 _self.customEvt.$on('scriptLoaded', function(moduleName) { if(moduleName == curModuleName) { buildDepsRelation(moduleName, depsTree); } }); } } }) } traverseScript(deps) }, // 一次收集全部依赖树的依赖 getAllDeps(initDeps, callback) { var _self = this; function traverseDeps(deps) { utils.each(deps, function(moduleName) { var curSrc = _self.getUrl(_self.baseUrl, moduleName); var isExistSrc = _self.srcs.includes(curSrc); // 判断相同的依赖是否已经加载过 if(!isExistSrc) { _self.srcs.push(curSrc); var script = utils.createScript(curSrc); script.onload = function() { var module = _self.deps[moduleName]; if(module.deps.length > 0) { traverseDeps(module.deps); }else { // 所有依赖收集完,才返回 var isDone = initDeps .map(entryDep => utils.isDepsDone({ depsTree: _self.deps, entry: entryDep })) .every(isDone => isDone === true); if(isDone){ typeof callback == 'function' && callback(_self.deps); } } } document.body.appendChild(script); }else { // 所有依赖收集完,才返回 var isDone = initDeps .map(entryDep => utils.isDepsDone({ depsTree: _self.deps, entry: entryDep })) .every(isDone => isDone === true); if(isDone){ typeof callback == 'function' && callback(_self.deps); } } }) } traverseDeps(initDeps) }, require(deps, factory) { typeof deps == 'string' && (deps = [deps]); // 迭代收集每一个入口依赖 this.traverseDeps(deps, function(depsTree) { console.log(depsTree); }); // 一次性收集所有依赖 // this.getAllDeps(deps, function(alldeps) { // console.log(alldeps) // }) } }); win.module = new module(); })(this);
源码链接: https://gitee.com/littleboyck/front/tree/master/front-module
联系方式:QQ: 1187253007