前端模块化探索(YUI篇)
接近一个月没写博客了,这次准备写几篇模块化的文章~
对于模块化java应该是这方面做的最好的,通过import加上包管理就能完全的拥有一套模块化的结构。另外在php5中引入的class机制也使php颠覆4以前的过程化编程模式,使php也拥有了模块化机制。
最初的js只是用于一些增强页面效果,在经历了静态html时代 - LAMP时代 最后到了现在的WEB APP的时代,现在的Web应用已经有大量的javascript代码,大型的企业级项目甚至js代码已经达到了c语言级别的几万行。模块化的需求越来越明显。
然而javascript由于自身语言的关系再加上它是运行在客户端的解释脚步本的原因在当时诞生时并没有考虑到模块化,本作者所谓的一夜情产物~不过这个娃还是相当优秀的,我们可以用对象hack加闭包的形式模拟模块。
一、基于YUI2的模块加载策略
面向企业级的YUI框架的对于命名空间的模块化划分还是不错的,YUI中主要声明了一个YAHOO对象,然后在这个对象继续开拓属性,其实本在js中没有命名空间这一个概念,但是通过在YUI中通过对单一的全局对象中属性附加达到能够命名空间的目的。
YAHOO.namespace("YAHOO.com.render"); (function(win,S){ //私有接口 var F = function(){ //TODO }; //开放的公共接口 S.doSomething = function(){ //TODO }; })(window,YAHOO.com.render);
如上代码:在YAHOO对象下申请了com.render这一命名空间,并将这个命名空间对象传入闭包,这里为保证命名空间不污染我们将所有的变量和私有函数都以var的形式在闭包内声明,而传入的命名空间下存放的方法和属性则是public的,对外能够调用其中的方法。
这种调用还是需要在app页面中一行一行的加入script引入标签,并且要按依赖顺序载入页面级。但这样的话会出现过多的http连接,对页面的载入效率不利~所以一般会再后端对于js进行combo压缩合并。
减少http连接的做法比较常用做法可以是:
/*merge start*/ (function(){ Import = { url:function(url){ document.write("<script type=\"text/javascript\" src=\""+url+"\"></scr"+"ipt>"); } } })(); /*merge end*/ Import.url("http://XXX/1.js"); Import.url("http://XXX/2.js"); Import.url("http://XXX/3.js");
如上的就可以直接在页面中引入,发布时在服务端可以按照merge注解进行压缩,这种方式这样发布后的页面级便只有一个引入的压缩脚本,而在本地调试时又可以通过脚本写入的方式进行,不会干扰本地开发,使线上环境和线下同步。
但是这种模块化的机制有很多缺陷。
一)、模块化机制没有涉及文件名与命名空间匹配问题,不能通过文件名引用模块
二)、模块引入没法做到模块内引入而是靠merge或是脚本页面写入
三)、依赖顺序控制由人工脚本引入或merge文件部署
四)、模块对应的css文件需要另行引入,导致页面js与css耦合性增大
五)、命名空间指针很容易被同名命名空间覆盖
二、YUI3基于配置式的动态模块机制
YUI3说它是个库更可以说它是个框架,YUI3中的模块机制是基于一种新的理念:比YUI2闭包式的模块更高一级的强沙箱式模块机制。
模块机制中最重要的方法包括add和use方法。
add方法提供对于组件的物理uri及模块命名配置,没有像YUI2中的对象hack命名空间的方式:
YUI().add('doSomething',function(Y){ Y.do=function(){}; } , version , {requires:['node','event']}); YUI().add('doSomething-v2', function(Y){} , version ,{ use:['base','upgrade'] });
add方法其实就是模块定义方法,new一个YUI对象后执行add方法,第一个参数是模块名,载入js后会将模块放到YUI.Env.mods这个map中储存在内存中,这样做的好处是保证模块的唯一性,而后就立即返回这个命名空间,回调方法其实是一个所谓的强沙箱,接受唯一的传入命名空间,其余的工作就是跟YUI2.8一样将私有用var 声明,而开放的接口直接在回调方法中传入参数中操作。
YUI3的add里面对于依赖顺序已经有了一定的进步。像第一段代码就是一个依赖的声明,而第二段代码则是多个模块的整合。
那如何调用上面定义的模块呢?比如上面第一段代码叫do.js
YUI({ modules: { 'doSomething': { fullpath: 'do.js', requires: ['node','event'] } } }).use('doSomething',function(Y){ //TODO });
可以看到在YUI3中已经提供了文件加载,其实上面的modules对象可以放到一个单独的config.js文件里,这样就只要在页面中引入种子脚本就能完成一切的模块操作,另外YUI3的级别已经上升到了框架级别,当一个库上升到框架级别就有可能与其他库共存,比如上面的do.js可以直接改为prototype.js,这样我们就能够调用prototype库了。虽然YUI模块化开发很爽,但这么多getScript的http请求页面加载会受到影响,对于这个问题YUI3自身提供combo的功能。
1.如果该模块之前没有被加载过(YUI.Env.Mods没有)且loader也没加载,则加载loader,回调重新调用use,结束 2.loader加载了,如果该模块之前没有被加载过(YUI.Env.Mods没有),则使用loader计算该模块的依赖项,逐一加载, 2.1 若可以combo,则先combo所有的css加载,调用_internalCallback ,然后combo所有的js加载 2.2 否则挨个加载(使用Y.GET _queue队列),先把所有的css加载完,调用_internalCallback,然后加载所有的js
小结:YUI2.8的模块机制是通过直接的hack对象来实现命名空间划分,在加载上下的功夫不够,而YUI3的精巧的动态模块化机制已经达到了一定的高度,但是后面出现的CommonJs规范使js的模块化标准到达了新的高度