Ruby's Louvre

每天学习一点点算法

导航

< 2025年2月 >
26 27 28 29 30 31 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 1
2 3 4 5 6 7 8

我的模块加载系统 v13

本版本最大的亮点是多版本共存。在一些大型项目,开发时间可能长达一年,其依赖的JS库,如jQuery可能在一年间升几次级。这时遇到的困难是该不该升级呢?基本上大家用jQuery的原因是它的插件多,这些插件作者与JR的水平差距大,没有他那样的功力与前瞻性,只保证在当前版本上运作良好。因此一升级就死一大堆插件,与rails有“插件杀手”的美誉。因此有些项目就出现不同版块使用不同jQuery的版本的情况了,项目反而被那些出于迅敏开发而引进的jQuery插件所累。

多版本共享的实现原理很简单,实现有效的代码隔离,而常用法就是命名空间。比如jQuery的命名空间就是jQuery与$,只有一层,它只能通过重命名减少与其他库或自己以前的版本冲突。但是jQuery的插件呢?它都是这样调用(function($){})(jQuery),如果改名并且这个$或jQuery被占用,就问题大了。为什么会出现jQuery被占用的情况呢?因为这个jQuery是jQuery1.42的那个命名空间对象啊,你又引进一个jQuery1.7,jQuery就被重写了。。。。因此我的框架mass Framework(即原来的dom Framework)一开始就有两个命名空间,隐藏的动态命名空间,它是根据URL生成的,另一个是面向用户的dom命名空间,并且这个dom放到动态命名空间上, window[httplocalhost8888indexhtml]["1.0"] = dom。如果页面上还加载了另一个版本呢?比如它的版本号是1.2,那么,window[httplocalhost8888indexhtml]["1.2"] = dom ;注意,这个dom与刚才的dom是不同一个对象。但这只是第一步。如何让用户调用它们呢?如果页面只有一个版本,用户直接使用dom.require("xxx",function(){}),如果存在第二版本(也就是后加进来的),那么它暴露到window下的名字是"dom"加版本号并且将点号改为下划线,即 "dom1_1", 使用时是 dom1_2.require("yyy",function(){})。不喜欢这么长的名字,简单,有两种方法:

var $ = dom1_2;
$(function(){
   $("#aaa").attr("title");//由于mass Framework的方法名与jQuery的方法名90%相同,直接使用就行了。
 })
dom1_2.exports("$");//使用自带的别名机制
$(function(){
   $("#aaa").attr("title");
 })

但作为一个框架,它注定不只有模块加载这样一个JS文件,还有许多其他JS文件来放置其他模块。在mass Framework中,模块是这样定义的:

dom.define("c","a,b",function(a,b){//依赖于a模块与b模块,回调函数的a,b参数是此两模块的返回值,如果没有为undefined
    dom.log("ccccccccccc");
    dom.log([a,b].join(","))
    return 3
})
dom.define("c",["a","b"],function(a,b){//模块定义的依赖列表可以是字符串,以逗号或空格分开,或者是字符串数组
    dom.log("ccccccccccc");
    dom.log([a,b].join(","))
    return 3
})

如果没有依赖模块,可以直接省去:

dom.define("c",function(){
    dom.log("c模块已加载")
    return 3
})

v13之前的模块定义比较麻烦,需要外包一层,即如下的样子

(function(global,DOC){    
    var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')];
    dom.define("data", "lang", function(){
        //数据缓存模块
        })
})(this,this.document);

v13已抛弃这种形式了。不过,有些人可能不解,这岂不是把dom命名空间暴露到全局作用域,如果这时我们改了名,再加载别的模块,那个模块岂不是找不到变量吗?与jQuery沦入同等境地?

不会的,插件是在iframe这样的沙箱环境中加载的。具体步骤是,创建一个临时的iframe节点挂在head上,然后再在iframe里面写入两个script,第一个把dom变量,指向父窗口中的隐藏命名空间上的对应版本号的dom对象。第二个利用模块名动用URL,加载JS文件,并设置相应的onload, onreadystatechange, onerror来捕捉回调与判定是否死链接。成功了就把这回调变成文本保存起来。

<!--比如为加载node模块会在iframe的head中写入如下内容(当前框架版本号为1.0b,运行浏览器为FF8,如果是IE8,下面的onload会变成onreadystatechange)
           dom.require("node",function(){
               alert(111)
           })
-->
<script> var dom = parent[document.URL.replace(/(#.+|\W)/g,'')]["1.0b"] ;</script><script src="http://localhost:8888/dom/node.js?timestamp=1321452993351" onload="if(/loaded|complete|undefined/i.test(this.readyState)){ dom._resolveCallbacks(); dom._checkFail('@node');} " onerror="dom._checkFail('@node',true);" ></script>

至于为什么变成文本保存起来?大家看一下那个经典的isArray问题就行了,不同window生成的对象不是能混用的。变成文本,再回传到父窗口eval一下,确保特征嗅探与其他操作准确无误。至此,模块加载时的同名冲突也解决掉了。

//凡是通过iframe加载回来的模块函数都要经过它进行转换
function safeEval(fn, args, str){
    for(var i = 0,argv = [], name; name = args[i++];){
        argv.push(rets[name]);
    }//如果是同一执行环境下,就不用再eval了
    if(fn instanceof Function){
        return fn.apply(global,argv);
    }
    return  Function("b","return " +(str || fn) +".apply(window,b)" )(argv);
}

总结一下,v13的改进:

  1. dom.log在打印页面时强制转换为字符串。
  2. 整合多版本共存到模块加载中,简化模块的定义。
  3. 添加getUid方法。

多版本共存的示例

相关链接:

我的模块加载系统 v12

我的模块加载系统 v11

我的模块加载系统 v10

我的模块加载系统 v9

我的模块加载系统 v8

我的模块加载系统 v7

我的模块加载系统 v6

如果您觉得此文有帮助,可以打赏点钱给我支付宝1669866773@qq.com ,或扫描二维码

posted on   司徒正美  阅读(4280)  评论(9编辑  收藏  举报

编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
历史上的今天:
2009-11-17 javascript 超级数组对象
2009-11-17 raphaeljs图形库学习笔记
点击右上角即可分享
微信分享提示