javascript【AMD模块加载器】浅析V3(添加CSS加载功能,重构内部流程)

由于今天正美大大的回复,然后前篇文章的评论里就出现了好多人站出来指责我抄袭,吓的我小心肝都扑通扑通的跳。

虽然这次的模块加载器是参照了正美大大的来写,但是至少我是一行一行代码自己写出来的。然后一个浏览器一个浏览器测试的,就连流程图都重画了好几次。

虽然大体上跟正美的差不多,但是细节上还是有很多不同的。看到哪些回复我也不想说啥。 抄没抄,会不会。明眼人一眼就能看出来,犯不着解释太多。

废话不多说,下面介绍这一版本的改进。额外增加了一个配置项控制并发的数量。因为浏览器的有效并发数是有限的。所以如果你一次性加载10个模块,就有可能阻塞掉其它的资源加载。

现在内部默认最大并发是4个。将原来的moduleCache变量删除,将所有加在信息都整合到modules中,并标记初始加载函数,在所有模块加载结束后删除初始加在函数。  

所有css加载,不计入模块加载中。而且加载css也不会在factory的参数中出现。也就是说如果你这样写也没关系。

require(['hello','test.css','test'], function(hello,test){

   console.log(hello,test); 

});

不过现在还有一个问题就是加载css检测加载完毕的问题。 由于浏览器对link标签的onload事件支持各不一样,加之就算为之做了兼容也是锦衣夜行。 因为根本不需要知道css的加载情况。

主要的改动就这些,还有一些细节性的改动。去掉了deps属性,检测循环依赖的方法改为使用args而不是之前的deps。将之前的loadJS方法改变为loadSource。 将require方法和load拆分开来。

使用文档碎片来将节点批量插入到页面中,尽量减少修改dom树,减少浏览器重排。

这一版中依然使用了正美大大博客中的哪个获得当前被解析的script的url的方法,不知道园子里的各位朋友有没有更好的办法。在最初的时候我是用模块名称来做唯一的,这样就不用获取正在解析的script的url。 但是重名模块就很难解决了。如果大家有更好的解决办法希望能告知一下。大家一起进步。 下面是最新的源码和使用的方法。

View Code
  1 ;(function(win, undefined){

  2     win = win || window;

  3     var doc = win.document || document,

  4         head = doc.head || doc.getElementsByTagName("head")[0],

  5         fragment = document.createDocumentFragment(),

  6         hasOwn = Object.prototype.hasOwnProperty,

  7         slice = Array.prototype.slice,

  8         configure = {total : 4},

  9         basePath = (function(nodes){

 10             var node, url;

 11             if(!configure.baseUrl){

 12                 node = nodes[nodes.length - 1];

 13                 url = (node.hasAttribute ? node.src : node.getAttribute("src", 4)).replace(/[?#].*/, "");

 14             }else{

 15                 url = configure.baseUrl;

 16             }

 17             return url.slice(0, url.lastIndexOf('/') + 1);

 18         }(doc.getElementsByTagName('script'))),

 19         _lynx = win.lynx;

 20 

 21     /**

 22      * 框架入口

 23      */

 24     function lynx(exp, context){

 25         return new lynx.prototype.init(exp, context);

 26     }

 27     

 28     lynx.prototype = {

 29         constructor : lynx,

 30 

 31         /**

 32          * 初始化

 33          * @param {All} expr 

 34          * @param {All} context

 35          * @return {Object}

 36          * 

 37          */

 38         init : function(expr, context){

 39             if(typeof expr === 'function'){

 40                 require('ready', expr);

 41             }

 42             //TODO

 43         }

 44     }

 45     lynx.fn = lynx.prototype.init.prototype = lynx.prototype;

 46 

 47     /**

 48      * 继承方法

 49      */

 50     lynx.fn.extend = lynx.extend = function(){

 51         var args = slice.call(arguments), deep = typeof args[args.length - 1] == 'bollean' ? args.pop() : false;

 52         

 53         if(args.length == 1){

 54             args[1] = args[0];

 55             args[0] = this;

 56             args.length = 2;

 57         }

 58 

 59         var target = args[0], i = 1, len = args.length, source, prop;

 60         

 61         for(; i < len; i++){

 62             source = args[i];

 63             for(prop in source){

 64                 if(hasOwn.call(source, prop)){

 65                     if(typeof source[prop] == 'object'){

 66                         target[prop] = {};

 67                         this.extend(target[prop],source[prop]);

 68                     }else{

 69                         if(target[prop] === undefined){

 70                             target[prop] = source[prop];

 71                         }else{

 72                             deep && (target[prop] = source[prop]);

 73                         }

 74                     }

 75                 }

 76             }

 77         } 

 78     };

 79 

 80     /**

 81      * mix

 82      * @param  {Object} target   目标对象

 83      * @param  {Object} source   源对象

 84      * @return {Object}          目标对象

 85      */

 86     lynx.mix = function(target, source){

 87         if( !target || !source ) return;

 88         var args = slice.call(arguments), i = 1, override = typeof args[args.length - 1] === "boolean" ? args.pop() : true, prop;

 89         while ((source = args[i++])) {

 90             for (prop in source) {

 91                 if (hasOwn.call(source, prop) && (override || !(prop in target))) {

 92                     target[prop] = source[prop];

 93                 }

 94             }

 95         }

 96         return target;

 97     };

 98 

 99     lynx.mix(lynx, {

100         modules : {               //保存加载模块

101             ready : {

102                 state : 1,

103                 type : 1,

104                 args : [],

105                 exports : lynx

106             }

107         },

108         urls : [],

109         loading : 0,

110         stacks : [],             //getCurrentScript取不到值的时候用来存储当前script onload的回调函数数组

111 

112         /**

113          * get uuid

114          * @param {String} prefix

115          * @return {String} uuid

116          */

117         guid : function(prefix){

118             prefix = prefix || '';

119             return prefix + (+new Date()) + String(Math.random()).slice(-8);

120         },

121 

122         /**

123          * noop 空白函数

124          */

125         noop : function(){

126 

127         },

128 

129         /**

130          * error 

131          * @param {String} str

132          */

133         error : function(str){

134             throw new Error(str);

135         },

136 

137         /**

138          * @return {Object} lynx

139          */

140         noConflict : function(deep) {

141             if ( window.lynx === lynx ) {

142                 window.lynx = _lynx;

143             }

144             return lynx;

145         }

146     });

147 

148 

149     //================================ 模块加载 ================================

150     /**

151      * 模块加载方法

152      * @param {String|Array}   ids      需要加载的模块

153      * @param {Function} callback 加载完成之后的回调

154      */

155     win.require = lynx.require = function(ids, callback){

156         ids = typeof ids === 'string' ? [ids] : ids;

157         var modules = lynx.modules, urls = lynx.urls, uuid = lynx.guid('cb_'), data;

158         data = parseModules(ids, basePath);

159         modules[uuid] = {

160             name : 'initialize',

161             type : 2,

162             state : 1,

163             args : data.args,

164             factory : callback

165         };

166         urls = urls.concat(data.urls);

167         lynx.load(urls);

168     };

169 

170     /**

171      * @param  {String} id           模块名

172      * @param  {String|Array} [dependencies] 依赖列表

173      * @param  {Function} factory      工厂方法

174      */

175     win.define = function(id, dependencies, factory){

176         if(typeof dependencies === 'function'){

177             factory = dependencies;

178             if(typeof id === 'array'){

179                 dependencies = id;

180             }else if(typeof id === 'string'){

181                 dependencies = [];

182             }

183         }else if (typeof id == 'function'){

184             factory = id;

185             dependencies = [];

186         }

187         id = lynx.getCurrentScript();

188 

189         dependencies = typeof dependencies === 'string' ? [dependencies] : dependencies;

190 

191         var handle = function(id, dependencies, factory){

192             var modules = lynx.modules, urls = lynx.urls;

193             modules[id].factory = factory;

194             modules[id].state = 2;

195             if(!dependencies.length){

196                 fireFactory(id);

197             }else{

198                 var data = parseModules(dependencies, id, true);

199                 urls = urls.concat(data.urls);

200                 lynx.load(urls);

201             }

202         }

203         if(!id){

204             lynx.stacks.push(function(dependencies, factory){

205                 return function(id){

206                     handle(id, dependencies, factory);

207                     id = null; dependencies = null; factory = null;

208                 }

209             }(dependencies, factory));

210         }else{

211             handle(id, dependencies, factory);

212         }

213     }

214 

215     require.amd = define.amd = lynx.modules;

216 

217     /**

218      * 解析加载模块信息

219      * @param {Array} list

220      * @param {String} path 

221      * @param {boolean} flag 

222      * @return {Object}

223      */

224     function parseModules(list, basePath, flag){

225         var modules = lynx.modules, urls = [], args = [], uniqurl = {}, id, result;

226         while(id = list.shift()){

227             if(modules[id]){ 

228                 args.push(id);

229                 continue;

230             }

231             result = parseModule(id, basePath);

232             modules[basePath] && modules[basePath].args.push(result[1]);

233             flag && checkCircularDeps(result[1], basePath) && lynx.error('模块[url:'+ basePath +']与模块[url:'+ result[1] +']循环依赖');

234             modules[result[1]] = {

235                 type : result[2] === 'js' ? 1 : 2,

236                 name : result[0],

237                 state : 0,

238                 exports : {},

239                 args : [],

240                 factory : lynx.noop

241             };

242             (result[2] === 'js') && args.push(result[1]);

243             if(!uniqurl[result[1]]){

244                 uniqurl[result[1]] = true;

245                 urls.push(result[1]);

246             }

247         }

248 

249         return {

250             args : args,

251             urls : urls

252         }

253     }

254 

255     /**

256      * parse module

257      * @param {String} id 模块名

258      * @param {String} basePath 基础路径

259      * @return {Array} 

260      */

261     function parseModule(id, basePath){

262         var url, result, ret, dir, paths, i, len, type, modname, protocol = /^(\w+\d?:\/\/[\w\.-]+)(\/(.*))?/;

263         if(result = protocol.exec(id)){

264             url = id;

265             paths = result[3] ? result[3].split('/') : [];

266         }else{

267             result = protocol.exec(basePath);

268             url = result[1];

269             paths = result[3] ? result[3].split('/') : [];

270             modules = id.split('/');

271             paths.pop();

272             for(i = 0, len = modules.length; i < len; i++){

273                 dir = modules[i];

274                 if(dir == '..'){

275                     paths.pop();

276                 }else if(dir !== '.'){

277                     paths.push(dir);

278                 }

279             }

280             url = url + '/' + paths.join('/');

281         }

282         modname = paths[paths.length - 1];

283         type = modname.slice(modname.lastIndexOf('.') + 1);

284         if(type !== 'js' && type !== 'css'){

285             type = 'js';

286             url += '.js';

287         }

288         return [modname, url, type];

289     }

290 

291     /**

292      * fire factory

293      * @param  {String} uuid

294      */

295     function fireFactory(uuid){

296         var modules = lynx.modules,

297         data = modules[uuid], deps = data.args,

298         i = 0, len = deps.length, args = [];

299         for(; i < len; i++){

300             args.push(modules[deps[i]].exports)

301         }

302         data.exports = data.factory.apply(null, args);

303         data.state = 3;

304         delete data.factory;

305         delete data.args;

306         if(data.type == 2 && data.name == 'initialize'){

307             delete modules[uuid];

308         }

309         checkLoadReady();

310     }

311 

312     /**

313      * 检测是否全部加载完毕

314      */

315     function checkLoadReady(){

316         var modules = lynx.modules, flag = true, data, prop, deps, mod, i , len;

317         for (prop in modules) {

318             data = modules[prop];

319             if(data.type == 1 && data.state != 2){    //如果还没执行到模块的define方法

320                 continue;

321             }

322             deps = data.args;

323             for(i = 0, len = deps.length; mod = deps[i], i < len ; i++){

324                 if(hasOwn.call(modules, mod) && modules[mod].state != 3){

325                     flag = false;

326                     break;

327                 }

328             }

329             if(data.state != 3 && flag){

330                 fireFactory(prop);

331             }

332         }

333     }

334 

335     /**

336      * 检测循环依赖

337      * @param  {String} id         

338      * @param  {Array} dependencie

339      */

340     function checkCircularDeps(id, dependencie){

341         var modules = lynx.modules, depslist = modules[id] ? modules[id].args : [];

342         return ~depslist.join(' ').indexOf(dependencie);

343     }

344 

345     /**

346      * create

347      * @param {String} type CSS|JS

348      * @param {String} url

349      * @param {Function} callback

350      */

351     function loadSource(type, url, callback){

352         var ndoe, modules = lynx.modules;

353         if(type == 'JS'){

354             var node = doc.createElement("script");

355             node[node.onreadystatechange ? 'onreadystatechange' : 'onload'] = function(){

356                 if(!node.onreadystatechange || /loaded|complete/i.test(node.readyState)){

357                     callback();

358                     node.onload = node.onreadystatechange = node.onerror = null;

359                     var fn = lynx.stacks.pop();

360                     fn && fn.call(null, node.src);

361                     head.removeChild(node);

362                 }

363             }

364             node.src = url;

365             modules[url].state = 1;

366             lynx.loading++;

367         }else if(type == 'CSS'){

368             var node = doc.createElement("link");

369             node.rel = 'stylesheet';

370             node.href = url;

371             delete modules[url];

372         }

373         node.onerror = function(){

374             lynx.error('模块[url:'+ node.src +']加载失败');

375             node.onload = node.onreadystatechange = node.onerror = null;

376             lynx.loading--;

377             head.removeChild(node);

378         }

379         return node;

380 

381     };

382 

383     lynx.mix(lynx, {

384         load : function(urls){

385             var loading , total = configure.total,modules = lynx.modules, url, node = fragment, type;

386             while((loading = lynx.loading) < total && (url = urls.shift())){

387                 type = url.slice(url.lastIndexOf('.') + 1).toUpperCase();

388                 node.appendChild(loadSource(type, url, function(){

389                     lynx.loading--;

390                     var urls = lynx.urls;

391                     urls.length && lynx.load(urls); 

392                 }));

393             }

394             head.insertBefore(node, head.firstChild);

395         },

396 

397         /**

398          * 加载JS文件

399          * @param {String} url

400          * @param {Function} callback 

401          */

402         loadJS : function(url, callback){

403             var node = loadSource('JS', url, callback)

404             head.insertBefore(node, head.firstChild);

405         },

406 

407         /**

408          * 加载CSS文件

409          * @param {String} url

410          * @param {Function} callback

411          */

412         loadCSS : function(url, callback){

413             var node = loadSource('CSS', url, callback);

414             head.insertBefore(node, head.firstChild);

415         },

416 

417         /**

418          * get current script [此方法来自司徒正美的博客]

419          * @return {String}

420          */

421         getCurrentScript : function(){

422             //取得正在解析的script节点

423             if (doc.currentScript) { //firefox 4+

424                 return doc.currentScript.src;

425             }

426             // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js

427             var stack;

428             try {

429                 a.b.c(); //强制报错,以便捕获e.stack

430             } catch (e) { //safari的错误对象只有line,sourceId,sourceURL

431                 stack = e.stack; 

432                 if (!stack && window.opera) {

433                     //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取

434                     stack = (String(e).match(/of linked script \S+/g) || []).join(" ");

435                 }

436             }

437             if (stack) {

438                 /**e.stack最后一行在所有支持的浏览器大致如下:

439                  *chrome23:

440                  * at http://113.93.50.63/data.js:4:1

441                  *firefox17:

442                  *@http://113.93.50.63/query.js:4

443                  *opera12:http://www.oldapps.com/opera.php?system=Windows_XP

444                  *@http://113.93.50.63/data.js:4

445                  *IE10:

446                  *  at Global code (http://113.93.50.63/data.js:4:1)

447                  */

448                 stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分

449                 stack = stack[0] === "(" ? stack.slice(1, -1) : stack;

450                 return stack.replace(/(:\d+)?:\d+$/i, ""); //去掉行号与或许存在的出错字符起始位置

451             }

452             var nodes = head.getElementsByTagName("script"); //只在head标签中寻找

453             for (var i = 0, node; node = nodes[i++]; ) {

454                 if (node.readyState === "interactive") {

455                     return node.src;

456                 }

457             }    

458         },

459 

460         /**

461          * 配置模块信息

462          * @param  {Object} option

463          */

464         config : function(option){

465             lynx.mix(configure, option);

466         },

467 

468 

469     //============================== DOM Ready =============================

470         

471         /**

472          * dom ready

473          * @param {Function} callback

474          */

475         ready : function (){                              

476             var isReady = false;

477             var readyList = [];

478             var ready = function(fn){

479                 if(isReady){

480                     fn();

481                 }else{

482                     readyList.push(fn);

483                 }

484             };

485 

486             var fireReady = function(){

487                 for(var i = 0,len = readyList.length; i < len; i++){

488                     readyList[i]();

489                 }

490                 readyList = [];

491                 lynx.modules.ready.state = 3;

492                 checkLoadReady();

493             };

494 

495             var bindReady = function(){

496                 if(isReady){

497                     return;

498                 }

499                 isReady=true;

500                 fireReady();

501                 if(doc.removeEventListener){

502                     doc.removeEventListener("DOMContentLoaded",bindReady,false);

503                 }else if(doc.attachEvent){

504                     doc.detachEvent("onreadystatechange", bindReady);

505                 }               

506             };

507 

508             if( doc.readyState === "complete" ) {

509                 bindReady();

510             }else if(doc.addEventListener){

511                 doc.addEventListener("DOMContentLoaded", bindReady, false);

512             }else if(doc.attachEvent){

513                 doc.attachEvent("onreadystatechange", function(){

514                     if((/loaded|complete/).test(doc.readyState)){

515                         bindReady();

516                     }                       

517                 });

518                 (function(){

519                     if(isReady){

520                         return;

521                     }

522                     var node = new Image();

523                     var timer = setInterval(function(){

524                         try{

525                             isReady || node.doScroll('left');

526                             node = null;

527                         }catch(e){

528                             return;

529                         }

530                         clearInterval(timer);

531                         bindReady();

532                     }, 16);

533                 }());

534             }

535             return ready;

536         }()

537     });

538     

539     win.lynx = lynx;

540 }(window));
//使用方法

//index.html 页面

<html>

    <head>

        <title>测试</title>

        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

        <script type="text/javascript" src="lynx.js"></script>

        <script type="text/javascript">

            lynx.require(['test.css','hello'], function(hello){

                alert(hello);

            })

        </script>

    </head>

    <body>

        <div class="test"></div>

    </body>

</html>



//test.css 文件

.test{

    width: 200px;

    height: 100px;

    background-color: red;

}



//hello.js 文件

define('hello', 'test',function(test){

    return 'a' + test;

});



//test.js 文件

define('test',function(test){

    return 'b';

});

 

你可能感兴趣的:(JavaScript)