前端基础框架的思考和尝试

开发 前端
前端基础框架是一直在思考类似的关于js模块和文件管理的方式,于是,经历了好几天的苦思冥想,稍微做了些尝试。下面会细细道来前端基础框架的详细内容。

近日我一直在思考类似的关于js模块和文件管理的方式。正好团队里也正有这样的需求,于是,经历了好几天的苦思冥想,稍微做了些尝试。下面会细细道来。

【js模块和文件的管理】

基于这个title,前提是我们已经明确了我们有了一个组件或者js methods 的lib,我们暂且把它叫做库,库里面存储了很多我们常用的东西,比如js插件,封装好的methods 
以及其他的一些lib组件。为了更好的管理我们这些颗粒化的js文件,我们的库通常都是呈颗粒化的。基于这种情况,我们可以说一个js文件就对应一个模块module,他有他相对独立的功能。这种管理模式是目前大多数主流框架的文件和模块管理模式,如YUI,EXT等,这样的好处是,可以按需调用。并且调用的模块一目了然。但是这样也有一个弊端,就是如果一个页面需要多个模块的支持,那么自然就需要加载对应的多个模块的js文件,http连接数自然会增加。这对网站的性能来说当然是不好的。所以,YUI等成熟的框架自然不会遗漏这个问题,他们也有一套自己注册和管理模块的机制(可以参考YUI的register和loader模块)

当然,jQuery凭借他易用的api风格和强大的选择器也赢得了很大的市场,但是我们通常喜欢把jQuery叫做一个方法库,而不是框架的原因是它相对于其他框架而言的话,对模块和文件的管理就稍逊一筹。虽然他后来的新版本也提供了自己的模块管理机制...

但是,这并不存在谁对谁错,谁好谁坏的问题,只是各自的侧重点不同而已。建站者选择谁只是看谁更适合自己而已。有些企业觉得YUI的架构模式更适合自己,于是选择了跟他相似的模式,于是有了百度的Tangram,淘宝的kissy,有的企业觉得jQuery更适合现在的自己,于是选择的jQuery,比如豆瓣,于是也有了克军的轻量级前端框架Do。我相信每个团队能够出一套自己的框架或者库都是不容易的,都是需要时间积累的,所以我从不轻易地评论别人的成果。

【主流的思路】

由于不是简单的把页面上加载的<script>转变成动态scriptNode添加,所以需要考虑的问题其实并不少。
比如我们要加载一个新模块a,对应的颗粒化文件为a.js,那么我们大概可以表示为
 

start loading -- a.js

 这当然是最简单的一种情况,复杂的情况通常都发生在存在模块依赖的时候,假如我们引用了jQuery,然后用jq写了一个插件a,那么我们加载顺序就得是:
 

start loading jQuery.js --> a.js

 如果模块依赖进一步复杂,比如:

a : require [b, c] //a 模块需要b,c模块  
b : require [c, d] //b模块需要c, d模块  
c : require [f]    //c模块需要f模块  
d : no-require  
f : no-require 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

 那么我们的加载顺序应该是怎么样的呢,就应该是:
 

Start loading: f.js --> d.js --> c.js --> b.js --> a.js
或者:         d.js 
--> f.js --> c.js --> b.js --> a.js

有人会想会不会存在循环依赖的问题,比如 a依赖b, b依赖a,这在理论上是不可能的,因为js是顺序执行的,如果出现循环依赖的情况的话就永远不知道谁在前谁在后...
基于这种思路,我们需要在调用的时候告知程序,a模块需要b模块和c模块,同时b模块又需要c和d模块,如果想复杂一点,这种循环的递归和排序以及判断重复加载的问题就很头疼。 

另:由于js是单线程的,一个模块的加载是需要时间的,而且没有编译过程,只有在运行到加载函数的时候再去找它所依赖的代码片段,有可能新模块还没加载完全,js仍在继续执行调用新模块中的方法,就会报错。解决的办法由两个,一个是创建一个阻塞的环境,让每次的加载函数的执行时间和加载时间一样长,不过如果这样的话其实就和在head里直接引用js文件的方式一样了。当所需加载模块过多时,性能永远是个大问题。
另外一个方法就是创建一个非阻塞的环境,通过回调函数来执行接下来的代码,这样在加载多模块时对性能有很大提升,就是函数体中的代码逻辑显得稍微乱一点。可以看到,YUI就是用的这种方式,另外豆瓣的Do也是这种方式。毕竟,性能为王。

 【script的插入方式】

 1.动态创建scriptNode,设置其src,插入到head中;

2.xmlHttp同步载入js,解析请求回来的reponseText,一是存在兼容性问题,还有跨域问题,以及对客户端网络高依赖的问题。

综上,相比较而言,采用的模式为***种。 

【目的】

 1.让js模块和html文档分离开来,让程序自动去探寻模块依赖的层级顺序,而不是编码者。
2.让js模块的调用和管理尽可能的容易,各取所需。
3.创建非阻塞的loading模式,提高页面性能。 

【使用】
由于篇幅所限,具体代码说明我就不细说了,这里放出源码,有兴趣的朋友可以看看:

/**  
 * Author : hongru.chen  
 * version : 0.1  
 * name : Module_v0.1.js  
**/ 
 
(function () {  
    if (typeof JS === 'undefined') arguments[0].JS = this.JS = {  
        scriptQueue:[] //用来加载的队列  
    };  
      
    var extend = function (destination, source, override) {  
        if (!override) override = true;  
        for (var property in source) {  
            if (override && !(property in destination)) destination[property] = source[property];  
        }  
        return destination;  
    };  
    var createScriptNode = function(url) {  
        var script = document.createElement("script");  
        script.type = "text/javascript";  
        script.charset = "utf-8";  
        script.src = url;  
        return script;  
    };  
    var head = function () {  
        return document.getElementsByTagName("head")[0]  
    }();  
      
    //递归遍历依赖模块,输出loading队列  
    var _queue = function (name) {  
        !JS._regedModules[name]['inQueue'] && JS.scriptQueue.push(name);  
        JS._regedModules[name]['inQueue'] = true;  
          
        if (JS._regedModules[name]['require']) {  
            var reqL = JS._regedModules[name]['require'];  
            for (var i=0; i < reqL.length; i++) {  
                if (JS._regedModules[reqL[i]]['inQueue']) return JS.scriptQueue;  
                JS._regedModules[reqL[i]]['inQueue'] = true;  
                JS.scriptQueue.push(reqL[i]);  
                  
                arguments.callee(reqL[i]);  
            }  
        } else {  
            return JS.scriptQueue;  
        }  
    };  
 
    // 添加入模块队列  
    JS.add = function (name, options, r) {  
        return new JS.add.prototype.register(name, options, r);  
    }  
      
    JS.add.prototype.register = function (name, options, r) {  
        if (!JS._regedModules) JS._regedModules = {};  
        if (JS._regedModules[name]) throw new Error('warning: module "'+name+'" is already added!');  
        JS._regedModules[name] = {};  
          
        if (typeof options == 'object') {  
            extend(JS._regedModules[name], options)  
        }  
        else {  
            var arg = arguments;  
            JS._regedModules[name]['url'] = arg[1];  
            if (!!arg[2]) JS._regedModules[name]['require'] = arg[2];              
            if (!!arg[3]) {  
                for (var i=3; i<arg.length; i++) {  
                    JS._regedModules[name][arg[i]] = arg[i];  
                }  
            }  
        }  
              
        this.add = function (n, p, r) {  
            return JS.add(n, p, r);  
        }  
          
        return this;  
    }  
      
    // 加载模块  
    JS.use = function (n, callback, logId) {  
        var _q = _queue(n) || this.scriptQueue;  
        this.load(_q, callback, logId);  
    }  
      
    JS.load = function (arr, callback, logId) {  
        var i = 0;   
        if (arr.length === 0) {   
            !!callback && callback();  
            return;  
        }  
        var curModule = arr.pop();  
          
        if (!!logId && typeof logId == 'string') {  
            var log = document.createElement('p');  
            log.innerHTML = 'module "' + curModule + '" is loaded';  
            document.getElementById(logId).appendChild(log);  
        }  
 
        __load(curModule, function () {  
            JS.load(arr, callback, logId);  
        });  
          
    }  
      
    __load  = function (name, cb) {  
        if (JS._regedModules[name]['isLoaded']) return;  
        var path = JS._regedModules[name]['url'];  
        var _s = createScriptNode(path);  
        head.appendChild(_s);  
          
        _s.onload = _s.onreadystatechange = function () {  
            if ((!this.readyState) || this.readyState === "loaded" || this.readyState === "complete" ) {  
                JS._regedModules[name]['isLoaded'] = true;  
                !!cb && cb();  
                _s.onload = _s.onreadystatechange = null;  
            }  
        }  
        _s.onerror = function () {  
            _s.onload = _s.onerror = undefined;  
            throw new Error (_s.src + ' loaded failed!');  
            head.removeChild(_s);  
        }  
    }  
})(window);  
  • 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.

引入了这样的模块管理机制后,所有的页面都只需要加载一个文件,就是上面的Module_v0.1.js;里面提供了一个命名空间JS,提供了两个主要的方法JS.add和JS.use.你可以这样使用它:

<html> 
    <head> 
        <script type="text/javascript" src="Module_v.01.js"></script> 
        <script type="text/javascript"> 
            JS.add(...)  
            JS.use(...)  
        </script> 
    </head> 
    <body>...</body> 
</html> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

 具体的使用方式可以如下:

JS.add('a', {  
    url : 'http://www.cnblogs.com/1293183133/js/***.js',  
    require : ['b','c']  
})  
JS.add('b', {  
    url : 'http://www.cnblogs.com/1293183133/js/***.js' 
})  
JS.add('c', {  
    url : 'http://www.cnblogs.com/1293183133/js/***.js' 
})  
 
JS.use('a'function () {  
    //you code  
})  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

 如果你觉得这样的调用方式太麻烦,可以写成方法链式的调用: add的***个参数是自定义的模块名,第二个参数可以是对象{},里面包含,对应模块的js文件的url,和此模块的依赖模块,如果没有就不写。
第二个参数也可以不是对象,比如上面例子里后面几个add,就直接是***个为模块名,第二个为对应模块的js文件url,第三个参数可选,为所依赖的模块名,是一个Array.

use 的***个参数为调用模块名,第二个参数为回调函数。 

【关注的几个问题】
1。模块会不会重复加载?
-- 不会,当判断此模块之前已加载过,就会直接执行回调。不会重复加载。

2。add的模块有重名时怎么处理?
--如果add的模块有重名,理论上是不允许,如果发现,会报错提醒。

3。add模块的时候需不需要考虑add的顺序?
--不需要,会自动甄别所依赖的模块,按依赖优先级载入。如果不放心,还提供了一个log日志功能,监测是否按正确顺序载入。只需在use方法调用的时候,写上第三个参数,如:

<div id="log"></div>  
<script>  
JS.use('a'function () {  
    //todo  
},'log')  
</script>  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

 第三个参数为所要显示载入信息的dom元素id。结果会如下显示:

 4。这样的方式对性能到底有没有好处?
--答案,有的,下面上普通的阻塞和非阻塞方式载入jQuery源码的对比图:
直接页面上载入js的方式
 

<script type="text/javascript" src="http://common.cnblogs.com/script/jquery.js?99"></script>  
  • 1.
 

***一条长长的载入时间就是jq的载入时间,故用户看到完整页面的时间为整个文件全部载入完成后的时间,大概为500ms左右(这单单是一个空文档载入jquery文件的时间)。 

以非阻塞方式载入时: 

<script type="text/javascript">  
JS.add('jq''http://common.cnblogs.com/script/jquery.js?111');  
JS.use('jq'function () {  
    alert($)  
})  
</script>  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

 

为了保证每次载入不会从缓存中读取,我加了个版本号?111;可以发现,上面的蓝线为用户感知到的页面download时间,在20ms左右,而jq文件的加载在蓝线后面,所以说这部分时间是用户感知不到的。对用户体验的提升应该是大有帮助的。就是给服务器增加了并发连接数。

好的,文章大概到这里,可能有人会说,基本上同样的事情Do.js,using.js,require.js等小众型框架已经都有了 ,为什么要自己再写?恩...别人的东西始终是别人的,自己做过的东西才真正是自己的。

我当然不敢说比别的大牛们考虑的更周到,效率更高,但是希望能在自己的编码中得到一点提高吧。

【编辑推荐】

  1. 前端之王能否续写辉煌 JavaScript服务器端开发现状
  2. Web开发有多难?前端后端都很烦
  3. 2010 Web前端技术趋势及总结 Facebook摘全明星MVP
  4. 基础知识:UPS电源系统防雷措施概述
  5. 亡羊补牢行不通 基础设施安全要与私有云建设动态适应
  6. 分布式Java应用:基础与实践
  7. PHP框架发展存四误区 死穴不除难成大器
  8. 小试一下微软开发框架LightSwitch
  9. 私有云部署实战:制定框架并建立配置数据库
责任编辑:佚名 来源: 博客园
相关推荐

2015-09-08 13:50:24

Web前端框架类库

2014-10-22 10:50:14

Web前端

2023-02-27 07:40:00

系统重构前端

2023-10-20 08:04:34

系统重构实践

2022-06-16 14:59:34

端到端语音翻译系统对话翻译翻译模型

2015-04-27 09:41:35

前端质量质量保障

2016-10-21 16:17:36

手淘Native框架和思考

2015-10-26 10:32:01

前端优化工程化

2015-07-14 10:11:48

前端框架语言

2013-10-24 10:40:23

前端框架

2023-02-20 08:41:08

SignaluseState()

2022-01-25 18:11:55

vdomclassfunction

2010-03-03 16:53:40

Linux Ubunt

2021-05-14 05:26:25

前端架构开发

2023-12-07 07:02:00

大仓权限设计

2017-07-10 14:53:35

前端开发MVVM模式有限状态机

2013-11-11 09:26:50

编程思考

2020-01-10 15:57:03

JavaScript开发 技巧

2020-01-10 10:48:27

JavaScript框架StateOfJS

2015-01-12 14:55:36

点赞
收藏

51CTO技术栈公众号