你好。这是我的第一篇博客。。。(此处应该矫情一万字)。
声明:
本文如果有任何地方让您引起不适,请不要喷我,因为可能过两天我自己再进来看得时候也会喷自己的。。人都有一个进步的过程,不是吗?
我会把实际工作过程当中遇到各种坑,各种疑难杂症,百度都不知道怎么输关键字的问题分享出来(胸口的红领巾更鲜艳了)。希望我的文章能够帮助到你。
另,转载请注明出处,谢谢!
进入正题吧。
单例模式的解决方案
js模块化开发的好处就不啰嗦了,但世上没有什么事物是完美的,无论是requireJs或是seaJs。其实完美这个词也是相对来说得,可能和每个人的设计思路,设计模式都有关系。
就拿本文即将说的所谓seajs的单例模式。就是无论你在页面的什么地方,哪个js文件里面,只要require/use的是同一个js,那么它们真的就是同一个。。。当初自己还傻傻地在一个模块里这么写过:
define(function (require, exports, module) {
var a1=require("a");
var a2=require("a");
a1.x="你又在装逼了?";
a2.x="不装逼我浑身难受!";
alert(a1.x);
alert(a2.x);
});
运行结果:
两个sb弹窗都在叫着:”不装逼我浑身难受”!说好的a1,a2呢?吓得我差点报警。
又进行了一番测试,a模块的内容如下:
define(function (require, exports, module) {
alert("a");
});
你会发现,哪怕你require一万次a,弹窗也只会出现1次哦~真得就出现1次哦~
而且提醒一下下:require也好,use也罢,对应的js文件都只会下载一遍!所以,不要担心同一个js文件多次require会消耗大量的网络资源导致的效率问题。
后来仔细一琢磨,这可能就是模块化开发的特点吧——单例模式,用它就忍着吧。。
问题是机智的我能忍吗?一点也不能惯着!而且原生JS也表示不服,说我特么好不容易发明出来的prototype,this,还有call等等这些能够让单身狗愉快地new一个对象的技能就这么被干掉了?弄得我好像是低级语言一样。。。(此处应有笑声)
上面这个需求如果不用seajs的话肯定是难不倒小伙伴们的:
function a(){};
var a1=new a();
var a2=new a();
//......后面自行脑补。
当初搜遍了网络,几乎没有发现过有类似的问题,更别提解决方案了,很是头疼,这也可能和本人的编程习惯有关,单例固然是好,但有时候实现某些需求得绕很大的弯子,敲一堆代码,这对于完美主义强迫症的我来说真真是个悲剧。。。
假设上面的a.js是我写的一个seajs模块,它是一个表格分页插件。在require了之后,我需要对其进行一些初始化的配置(绑定表格,设置回调等等)。那么问题来了,如果当前页面上只有一个分页表格一切都好说,该怎么配置插件就怎么配置,那么多个表格呢?我每次调用其方法都把对应的配置给传过去?说实话,我干不出这种蠢事。。于是好戏来了,如果你给a模块内部”exports”上下面这个神奇的函数,包你腰不酸了,腿不疼了,敲起代码也更有劲了
define(function (require, exports, module) {
//hi~我是随便找个地方插进来的new方法
exports.newInstance = function (fuc) {
return function () {
fuc += "";
if (fuc.indexOf("\nexports=this;") == -1) {
fuc = ("0,(" + fuc + ")").replace("{", "{\nexports=this;");
var p = /([\s;=]+require\s*\([\s\S]*?\))/g;
fuc = fuc.replace(p, "$1.newInstance()");
p = /( *\. *newInstance *\( *\)){2}/g;
fuc = fuc.replace(p, ".newInstance()");
}
return new (eval(fuc))(require,exports, module);
}
}(arguments.callee);
});
别问为什么,看上去有点蒙,仔细一琢磨,卧槽!原来是酱紫啊!就对了,我的装逼目的也就达到了。
于是a模块的多例使用代码如下:
define(function (require, exports, module) {
var a1=require("a");
var a2=a1.new();
a1.doSth();
a2.doSth();
//......后面自行脑补
});
注意:
通过本函数new出来的模块如果内部还有依赖,你得给它依赖的模块也实现一个new方法,大家new才是真的new,这样出来的模块才是干干净净地,完全独立的模块。当然了,某些依赖链里的模块可能真得不需要重新创建实例,本着浪费就是可耻的原则,你可以把它的newInstance方法这么写:
define(function (require, exports, module) {
//hi~我是随便找个地方插进来的不会创建新实例的new方法
exports.newInstance= function () {
return exports;
}
});
模块记载机制
千万别天真的以为你在模块里面这样:
//不要在意a,b,c,d这种命名方式。。。
define(function (require, exports, module) {
var a=require("a");
var b=require("b");
});
就天真地以为,这个模块会加载完a模块之后再加载b模块。。
真相其实是这样的:
在一个模块里面,require语句的优先级是最高的,无论你将它放在模块的任意位置会优先执行require。
注意,优先执行require不代表你可以这么写:
define(function (require, exports, module) {
alert(a.x);
var a=require("a");
a.x=1;
});
除非a.js是个三方插件或者a模块不对外提供任何方法和属性,仅仅是用于给页面来点特效啦,加载一些数据啦等,和代码的下文没有什么关系的事情,你大可以把require(“a”)放在任何地方,也就没必要将其赋值给某个变量了。
比如:
a.js的内容如下:
//a.js的全部代码就这一行 是一个并没有遵循seajs规范的三方插件
alert("a");
一个依赖a模块的模块内容如下:
define(function (require, exports, module) {
alert("t");
require("a");
});
执行结果你会发现弹窗内容果然先是”a”,后是”t”;
值得注意的是seajs还有一种模块加载方法是use,它的加载时机就得看你把它写在什么地方了。
接上文的那个依赖a模块的模块内容如果是这么写:
define(function (require, exports, module) {
alert("t");
seajs.use("a");
});
这次的执行结果就是先”t”后”a”了。
关于上面这一点,后面我会继续说。
言归正传,a和b到底谁先加载,我可以肯定地告诉你,确实是a先加载,但是!恩,但是来了。但是a和b谁先加载完就得看各自的造化了,或是因为网络原因,或是因为文件大小,意思就是说你require的模块它们之间一定不要有啥耦合关系,否则很有可能会因为加载的先后顺序导致一些异常,不过话又说回来,如果你严格按照seajs的规范来设计模块的话这种事问题肯定不会出现的,各个模块内部都有独立的作用域和其明确的依赖链嘛。。
举一反四:
define(function (require, exports, module) {
seajs.use("a");
seajs.use("b");
});
a和b的加载情况和上述是一样一样的。
如何使用非seajs插件
不得不承认,目前的seajs插件少得令人发指!在实际开发过程中,总会用到各种各样五颜六色的插件,它们不是jquery插件就是jquery插件!咱们不可能说一个项目当中的所有插件都自己用seajs的规范再重复造一个吧?不说时间够不够了,就算够,你造出来之后总得优化,总得调bug吧?哪有直接用市面上已经现成的,修炼多年的插件来得爽快?就算你还是说,不行!我就是喜欢重复造轮子!我就是觉得自己写的插件是最好的,别人的就是垃圾!shit!哥们,我太喜欢你了,其实你和我一样,我还真是不喜欢用别人的插件,不说别人写得好不好吧,其实主要讨厌还得去看他们出的文档,我打死都不会承认我看不懂某些充满了浓浓装逼气息的api文档的。。我靠,我又说废话了!
结合上文说的seajs加载机制,如果想要使用三方插件,比如:jquery-table-xxx.js,显然,它是一个jQuery插件,而且显然还依赖jquery-table.js。
也就是有了如下依赖关系:
- jquery-table-xxx依赖jquery-table
- jquery-table依赖jquery
假设jquery.js内容如下:
var a=1;
jquery-table内容如下:
var b=a+1;
jquery-table-xxx内容如下:
var c=b+1;
主js内容如下:
seajs.use("jquery",function(){
seajs.use("jquery-table",function(){
seajs.use("jquery-table-xxx");
})
})
alert(c);
思考: 主js的运行结果一定是3吗?
思考结束,答案是也许是3。一定要搞明白一点,主js中的alert语句虽然一定会在jquery-table-xxx.js加载之后执行,但不一定是jquery-table-xxx.js加载完毕之后执行!加载之后,加载完毕之后是不一样滴~骚年!。只有这点搞懂了,才会知道为什么seajs的项目中使用三方插件会出现一些随机错误!什么?你问为什么不在各个js文件里require各自的依赖?都说了别人写的插件,并没有遵循seajs规范,你也就不能使用require了!懂?
肯定有小伙伴会说,如果主js这么写:
seajs.use("jquery",function(){
seajs.use("jquery-table",function(){
seajs.use("jquery-table-xxx",function(){
alert(c);
});
})
})
不就完事大吉了吗?是的。。但是你不觉得这种嵌套太变态了么。而且如果除了上述的三个插件,再来些插件:lalala.js,lalala-xxx.js,你单纯use的话。。。
主js要这样吗?
seajs.use("jquery",function(){
seajs.use("jquery-table",function(){
seajs.use("jquery-table-xxx",function(){
seajs.use("lalala",function(){
seajs.use("lalala-xxx",function(){
alert(c);
//这里省略使用lalala-xxx模块的代码。。编不下去了
});
});
});
})
})
卧槽!!哈哈,而且重点是lalala,lalala-xxx和另外三个js是没有半毛钱关系的,凭什么要它俩等那三个加载完毕再加载?效率呢?
那么主js要这样吗?
seajs.use("jquery",function(){
seajs.use("jquery-table",function(){
seajs.use("jquery-table-xxx",function(){
alert(c);
});
})
})
seajs.use("lalala",function(){
seajs.use("lalala-xxx",function(){
//这里省略使用lalala-xxx模块的代码。。编不下去了
});
});
不错,加载效率是上去了,那么代码的可读性呢?业务逻辑代码会被这些加载代码给拆得七零八落
于是下面我要写的就是一个能够让小伙伴们愉快地使用三方插件的插件。。
- 打开记事本(都是装逼界的同仁,你就当做我打开的是记事本呗)
- 新建文件->superUse.js(是时候来个响亮的名字亮瞎你们了)
- 内容如下:
define(function (require, exports, module) {
var newUse = function (array, fuc) {
this.num = array.length;
this.fuc = fuc;
for (var i = 0; i < array.length; i++) {
this.loadJs(array[i]);
}
}
newUse.prototype.loadJs = function (array) {
var that = this;
if (typeof(array) == "string") array = [array];
var callBac=(array.length == 1?function(){
if (--that.num == 0){
if (that.fuc)that.fuc();
}
}:function(){
array.shift();
that.loadJs(array);
})
seajs.use(array[0],callBac);
}
exports.use = function (array, fuc) {
new newUse(array, fuc);
}
});
调用示例:
define(function (require, exports, module) {
var superUse=require("superUse");
superUse.use([
"a",
["b","c"],
["e","f"]
],function(){
alert("所有js全部加载完毕的回调函数");
})
//参数1(数组):["a",["b","c"],["e","f"]]
//a是个独立的三方js插件
//c依赖b为一个三方插件
//f依赖e为一个三方插件
//它们三个会同时按照各自的组内的依赖关系同时加载。
//参数2(function):function(){alert("所有js全部加载完毕的回调函数");}
//表示上述的js全部加载完毕之后会执行该回调函数,建议把业务逻辑代码全部丢进去,这样能够保证它们的执行是在三方插件全部加载完毕之后
});