-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpeasjs.js
568 lines (509 loc) · 21.6 KB
/
peasjs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
/*
* Created with Vim7.3 ubuntu10.04
* @fileOverview : peasjs加载器
* @author : Chen weihan <[email protected]> 飞火
* @since : 2013年11月18日 星期一 21时32分17秒
* @filename : peasjs.js
* @version : v1.0
* @description : 这里有3个难点: 1:引入文件的依赖的解决 2:执行函数的多依赖多层嵌套的解决 3:全局变量的先后使用的混乱
* 如果回调要输出document.wirte(),需要按照下面写法,具体原因在document.write.
* document.open();
* document.write("1111#可显示的文字内容<br/>");
* document.close();
*/
;(function(win){
/**
* moduleTemp :{
* name : {
* name : name, //初始模块名
* fordeps : { //每个模块为KEY,单独保存当前状态的依赖链,因为初始模块的树形依赖存在异步加载,共享读写fordeps,会造成该变量混淆.
* a : all-deps
* b : all-deps
* },
*
* }
*
* 模块缓存
* moduleCache : {
* name : {
* name : name, //模块名
* callback : callback, //回调
* state : 0, //加载状态
* deps : deps //依赖
* }
* }
*
*/
var debug = false, //模块不会缓存,同时浏览器请求的js都是最新的[调试打开后,调试dome4.html即可明白]
modulePath = 'modules/', //载入js的路径
componentPath = 'component/', //载入js 组件的路径
moduleTemp = {}, //用户use启始模块,临时保存,以方便找出依赖链
moduleCache = {}, //缓存module模块
nestedLevel = 10, //保险机制,递归最多层级限制,以防检测递归树依赖异常,卡死。
defsBool = false; //判断依赖死循环.
/*********************私有方法*******************/
//私有方法
var method = {};
/**
* 加载
* @param name 模块名
*/
method.load = function(name) {
var data = moduleCache[name],
names = data.deps;
//循环载入js
for (var i=0 ; i < names.length; i++ ) {
//console.log('loaded module',names[i]);
if (this.beforeCreate(names[i])) {
this.create(names[i]);
//console.log('loaded define module',names[i]);
}
}
};
/**
* 创建模块加载前处理
* @param name 模块名
*/
method.beforeCreate = function(name) {
var bool = true;
//判断缓存,如果没有缓存,缓存到队列
if (typeof(moduleCache[name]) == 'undefined') {
moduleCache[name] = { //这里加入cache,而不是异步返回的时候加入是避免一个加载很久的模块,在异步加载没有返回前,请求多次就加载几次都等着异步返回。
name : name,
state : 0,
};
} else {
bool = false;
}
return bool;
};
/**
* 清除缓存http
*/
method.path = function(name) {
var path='',d = new Date();
if (name.indexOf(componentPath) > -1) {
path = name;
} else {
if (name.indexOf('.css') > -1) {
path = modulePath + name;
} else {
path = modulePath + name + '.js';
}
}
if (debug) {
path = path+"?time="+d.getTime();
}
return path;
};
/**
* js/css分别载入
*/
method.create = function(name) {
if (name.indexOf('.css') > -1) {
method.createCss(name);
//css的文件状态,默认为state=1,css之间不存在依赖关系,其他依赖仅存在渲染。
moduleCache[name].state = 1;
} else {
method.createJs(name);
}
};
/**
* 创建dom,载入js
* @param name 文件名
*/
method.createJs = function(name) {
var head = document.getElementsByTagName('head').item(0),
script = document.createElement("script");
script.type = "text/javascript";
script.src = this.path(name);
script.async = true;
script.onreadystatechange = script.onload = function() {
if (!this.readyState || this.readyState=='loaded' || this.readyState=='complete') {
head.removeChild(script);
script = null;
method.afterLoaded(name);
}
};
head.appendChild(script);
};
/**
* 创建dom,载入css
* @param name 文件名
*/
method.createCss = function(name) {
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = this.path(name);
document.getElementsByTagName("head")[0].appendChild(link);
};
/**
* 载入后响应
*/
method.afterLoaded = function(name) {
//console.log(name);
if (name.indexOf(componentPath) > -1) {
moduleCache[name].state = 1;
method.checkComponentDeps();
}
};
/*
* 检测关联依赖是否完成
* @param name
* load页面如果是异步载入,每次都需要判断根的依赖,如果是同步,只需要判断,当前页的依赖即可.
* 1:回调函数的执行值,不能缓存到moduleCache,只能缓存回调函数.因有些模块是即时性的,例如获取当前时间,生成随机id.
* 2:依赖关系的状态值,不能使用一个全局变量,需要使用全局变量依赖当前模块来为key保存,因当多个模块异步回来相近时间时,全局会造成属性值混乱.
* 每个use进来的模块(启始模块),都需要这样一个缓存变量,所以直接挂接到moduleTemp对应的起始模块的属性fordeps
*
*/
method.checkDeps = function(name) {
var bool = false;
//从起点模块开始循环判断 如果moduleTemp中的模块执行完毕,会delete掉
for (var i in moduleTemp) {
//single 与 pidObj 的区别在于 pidObj是随机key,这个给判断父级依赖有关,single的key是moduleName给依赖执行函数有关.
var single = {}, //每个起点模块的依赖链模块,最后函数执行需要的临时缓存.
pidObj = {}, //判断树结构单链死循环,判断父级节点是否存在相同即可.
id = this.randomNum('m'), //随机id,pid,主要用于闭环父级查找死循环
fdeps = moduleTemp[i].deps;
//初始化当前模块对象
moduleTemp[i].fordeps[name] = {};
//递归检测依赖加载
single[i] = {
name : i, //模块名
deps : fdeps, //依赖
state : 0, //函数执行状态
exports : {} //缓存函数执行返回值
};
//递归检测树单链闭环
pidObj[id] = {
name : i, //模块名
id : id, //随机id,pid,主要用于闭环父级查找死循环
pid : ''
};
//依赖递归检测
this.forDeps(fdeps,i,name,single,pidObj,id);
//每个模块载入判断是否加载完成
bool = this.allLoaded(single);
if (bool && !defsBool) {
this.fireFactory(i,single);
//use的起点是随机的模块名,不可能在调用,清除以节约内存
delete moduleTemp[i];
delete moduleCache[i];
}
}
};
/**
* 检测依赖是否载入完毕
* @param startModule 起点模块
* @param currentModule 当前模块
* @return bool
*/
method.allLoaded = function(single) {
var bool = true;
for (var i in single) {
var state = moduleCache[single[i].name].state;
if (!state) {
bool = false;
break;
}
}
return bool;
};
/**
* 触发回调函数
* @param startMOdule 起点模块
* @param obj 所有依赖链函数结构队列
*/
method.fireFactory = function(startModule,obj) {
//递归拼接执行函数
//console.log(startModule,obj);
var bool = true;
for (var i in obj) {
if (!obj[i].state) {
method.fireFactoryHandle(obj[i],obj);
bool = false;
}
}
if (bool) {
obj = null;
} else {
arguments.callee(startModule,obj);
}
};
/**
* 依赖链函数执行,及保存结果
* @param mod 当前依赖函数
* @param obj 整个依赖链函数队列
* 只返回当前依赖模块的对象,只能调用当前依赖的模块
*/
method.fireFactorySingleHandle = function(mod,obj) {
//判断依赖
var deps = mod.deps,
callback = moduleCache[mod.name].callback;
if (deps.length > 0 ) {
var bool = true,args=[];
for (var x in deps) {
if(!obj[deps[x]].state) {
bool = false;
} else {
args.push(obj[deps[x]].exports);
}
}
if (bool) {
obj[mod.name].state = 1;
obj[mod.name].exports = callback.apply(null,args);
}
} else {
obj[mod.name].state = 1;
obj[mod.name].exports = callback();
}
};
/**
* 返回所需依赖链,包括依赖的依赖的对象
* @param mod 当前依赖函数
* @param obj 整个依赖链函数队列
* 这样更灵活,可以调用依赖链上任意提供对外的接口对象
*/
method.fireFactoryHandle = function(mod,obj) {
//判断依赖
var deps = mod.deps,
bool = true,
exports = {},
callback = moduleCache[mod.name].callback;
if (deps.length > 0 ) {
for (var x in deps) {
if(!obj[deps[x]].state) {
bool = false;
} else {
var exObj = obj[deps[x]].exports;
for (var i in exObj) {
exports[i] = exObj[i];
}
}
}
if (bool) {
obj[mod.name].state = 1;
exports[mod.name] = callback(exports);
obj[mod.name].exports = exports;
}
} else {
obj[mod.name].state = 1;
exports[mod.name] = callback({});
obj[mod.name].exports = exports;
}
};
/**
* 生成随机数唯一
*/
method.randomNum = function(prefix) {
return prefix + (new Date().getTime()) + Math.floor(Math.random()*100000);
};
/**
* 判断依赖数单链是否存在死循环,上线10依赖 是否可以优化判断自己是否存在依赖即可
*/
method.infiniteLoops = function(id,pidObj,name,level,debug) {
if (level > 0) {
level--;
if (id !== '') {
var pid = pidObj[id].pid;
if (pid !== '') {
//console.log('for',pidObj[pid].name,name);
debug += '->'+pidObj[pid].name;
if (pidObj[pid].name !== '' && pidObj[pid].name == name) {
console.log('异常:'+name+'模块载入检测依赖发现闭环,运行失败。具体依赖链'+debug);
defsBool = true; //全局修改判断依赖死循环
return;
} else {
arguments.callee(pid,pidObj,name,level,debug);
}
}
}
} else {
defsBool = true;
console.log('依赖嵌套超过配置的层级了,如是正常情况,请增加配置层级数!!!');
return;
}
};
/**
* 递归循环检测模块依赖
* @param fdeps 依赖
* @param startModule 起点模块
* @param currentModule 当前模块
* @param single 缓存队列
* @param pidObj 缓存对象检测死循环
*/
method.forDeps = function(fdeps,startModule,currentModule,single,pidObj,pid) {
if (typeof(fdeps) !== 'undefined' && fdeps.length > 0) {
if (defsBool) {return;} //跳出递归
for (var x in fdeps) {
if (defsBool) {break;}; //跳出循环
var depModule = fdeps[x],
id = method.randomNum('m'),
module = moduleCache[depModule],
ffdeps = module.deps;
pidObj[id] = {
name : depModule,
id : id,
pid : pid
};
single[depModule] = {
name : depModule,
deps : ffdeps,
state : 0,
exports : {}
};
//判断检测依赖树单链死循环
method.infiniteLoops(id,pidObj,depModule,nestedLevel,depModule);
if (defsBool) {
if (typeof(console) !== 'undefined') {
console.log('加载完'+currentModule+'模块,检查依赖出现循环依赖,停止依赖解析,该模块运行失败!');
} else {
alert('加载完'+currentModule+'模块,检测依赖出现循环依赖,停止依赖解析,该模块运行失败!');
}
break;
} else {
method.forDeps(ffdeps,startModule,currentModule,single,pidObj,id);
}
}
}
};
//第三方组件引入,依赖只有一级,不会递归查找依赖
method.checkComponentDeps = function() {
for (var i in moduleTemp) {
if (moduleTemp[i].name.indexOf(componentPath) > -1) {
method.callbackComponent(moduleTemp[i]);
}
}
};
//第三方库的回调
method.callbackComponent = function(mod) {
var deps = mod.deps,
bool = true,
exports = {},
callback = moduleCache[mod.name].callback;
if (deps.length > 0 ) {
if(!moduleCache[deps[0]].state) {
bool = false;
} else {
var deps2 = moduleCache[deps[0]].deps;
for (var i in deps2) {
if(!moduleCache[deps2[i]].state) {
bool = false;
}
}
}
if (bool) {
var exports = moduleCache[deps[0]].callback();
callback(exports);
}
} else {
callback({});
}
};
/**********************加载器********************/
var peas = {};
//引入模块
peas.use = function(deps,callback,parentName) {
var type = Object.prototype.toString.call(deps).slice(8, -1);
if (type == 'Array') {
peas.useHandle(deps,callback,parentName);
} else if (type == 'Function') {
peas.useHandle([],deps,parentName);
}
}
peas.useHandle = function(deps,callback,parentName) {
var name = parentName || deps.join( '_' ) + '_' + ( +new Date() ) + ( Math.random() + '' ).slice(-8);
//合并require方式引入的模块
deps = peas.requireDeps(callback,deps);
//判断初始模块使用
if (typeof(parentName) == 'undefined') {
var id = method.randomNum('m');
if (debug) {
moduleTemp = {};
moduleCache = {};
}
//建立依赖起点
moduleTemp[name] = {
uid : id,
name : name,
deps : deps,
fordeps : {}
}
//use 本身也是一个模块,没有名称而已,随机生成的,建立模块队列
moduleCache[name] = {
name : name,
callback : callback,
state : 1,
deps : deps
};
}
//加载文件
method.load(name);
};
peas.require = function(moduleName){
//return exports[moduleName];
//return 'test';
};
//匹配依赖
peas.requireDeps = function(callback,deps) {
var str = callback.toString(),
arr = [],
patten = new RegExp(/peas.require\(['|"]([\w|\\|\/]+)['|"]\)/gi),
result = patten.exec(str);
while (result != null) {
arr.push(RegExp.$1);
result = patten.exec(str);
}
if (typeof(deps) == 'undefined') {
deps = [];
}
return arr.concat(deps);
};
/**
* 初始化配置,lib一定的先加载进去
{
//***********系统配置*********
//路径
modulePath : "modules/",
//组件
componentPath : 'component/',
//共用库ccs/js 只负责引入,全局的.
lib : ['jquery.js','util.js'],
//***********自定义配置***************
//api调用前缀
api : '',
........
}
peas.config = function(option) {
var lib = (option.lib == undefined ? option.lib : []);
};
*/
/*********************外部接口*******************/
/**
* define 模块方法
* @param name 模块名
* @param deps 模块依赖关系
* @param callback 模块回调函数
*/
win.define = function(name,deps,callback) {
//判断是否继续执行依赖模块
//console.log('defined',name);
if (!defsBool) {
moduleCache[name].callback = callback,
moduleCache[name].deps = deps;
moduleCache[name].state = 1;
if (deps.length > 0) {
peas.use(deps,callback,name);
}
//判读是否是第三方引入
if (name.indexOf(componentPath) > -1) {
method.checkComponentDeps(name);
} else {
//检测依赖,执行回调函数
method.checkDeps(name);
}
}
}
//peas对外接口
win.peas = peas;
})(window)