本文参考《JavaScript设计模式与开发实践》(曾探,中国工信出版集团)以及网络上资源,看看就行,别信以为真
JavaScript(下面简写成JS吧)作为一个动态类型类型,面向对象的实现,依靠的是原型委托的方式;
至于什么动态类型、多态、原型就不说了,就说说一些不好弄明白的地方吧:
1.this与this的指向
JS的this和C++的this区别蛮大的,它是按层级获取当前环境的对象来进行指向的,所以就是说可以对其进行保存和赋予:
一般来说局部函数、返回函数想要保留函数体的this指向可以直接赋值:
var original = function(){
var thisKeep = this;
var callback = function(){
func(thisKeep);
};
callback();
};
返回函数的保存则是利用了闭包,后面说
赋值就是call、apply了
2.Function.prototype.call 与 Function.prototype.apply
这两个是Function对象的原型方法,可以改变Function中的this的指向
上面的original 还可以这么写:
var original = function(){
var callback = function(){
func(this);
};
callback.call(this);
};
call和apply二者区别只是参数的传入方式稍有差距而已,利用apply可以实现环境bind
Function.prototype.bind = function(context){
var self = this;
return function(){
return self.apply(context,arguments);
}
};//这个是无参数形式的bind
3.作用域与闭包
变量在声明时决定了他的作用域以及生命周期,对于函数的局部变量一般在函数执行完成后就会销毁:
var funcplus = function(){
var a=1;
return function(){
a++;
console.log(a);
}
};
var objectFun = funcplus ();
objectFun (); //2
objectFun (); //3
objectFun (); //4
这个时候a的值并不会销毁,类似于类变量的形式,var objectFun = funcplus ()其实就类似于一个Function对象的实例化,而funcplus 这个函数就是一个闭包,实例化(JS中应该是返回一个原型的引用)的时候a的执行环境会被记录并可以被外界访问;
闭包可以封装变量、延续局部变量的寿命,MV中动态载入图像文件的时候就是利用这一点
4.函数的传递与自执行
函数作为一种特殊的数据,可以被当做参数,也可以当做返回值,常见的形式就是回调、迭代、bind等
匿名函数一般需要自执行,这样也方便进行闭包处理:
var Type = {};
for(var i=0,type;type = ['Null','String','Boolean','Number','Array','Object','Function'][i++];){
(function(type){
Type['is'+type] = function(obj){
return Object.prototype.toString.call(obj) === '[object '+type+']';
}
})(type)
};
这里实现简单的数据类型判别:
Type.isString('dddd');
Type.isArray([]);
设计模式,并不针对结构,只针对意图,也就是说模式实现意图的一些较好的方式
下面说说几个设计的模式:
1.单例模式
参看RMMV,有很多对象想要实现的便是这种全局仅此一个实例,但是MV的Manager利用的是全局变量,而一堆$data更是如此,无原型的绑定,操作起来十分费力,而Game_系列则更应该都是单例,然后并没有。
单例实现方式有很多,充分利用JS的特性的单例模式应该如下:
var getSingle = function(func){
var resobj;
return function(){
return resobj || (resobj = func.apply(this,arguments));
}
};
2.发布订阅模式
参看RMMV的Input模块,在玩家输入时,其实并不是马上得到响应,而是等到下一个Input.Update时才会反应,有没有一种办法让Input主动去通知Scene进行反应呢?就是这个意图,出现了这种模式:
这种理念和回调是一致的,不是Scene一直去询问Input,而是Input在接受了Dom中的Event触发的按键、点击后直接通知Scene:
大致实现:
var Input = {};
Input.listenClient = [];
Input.listen = function(fn){
this.listenClient.push(fn);
}
Input.trigger = function(){
for(var i=0,fn;fn=this.listenClient[i++];){
fn.apply(this,arguments);
}
}
而Scene注册方法——使用下面这种形式即可:
Input.listen(function(){
.....
})
Input触发使用trigger(args),将参数传给所有的订阅者执行相应的操作
3.装饰者模式
JS的插件脚本中想要修改本来的原型,通用的方法就是:
var old_func = func || function(){};
func = function(){
...//do something before
old_func();
...//do somthing after
}
这里会有一些小问题:①old_func这个新的变量会变得让人不爽,②this有可能被劫持,比如你修改Manger系列的一些全局函数,当然可以这样这个修改防止问题:(假设这个func是Manger的函数)
old_func.apply(Manger,arguments);
}
下面提供一种办法装饰原函数实现脚本书写:
Function.prototype.before = function(func){
func.apply(this,arguments);
return _self_ .apply(this,arguments);
}
Function.prototype.after= function(func){
var res = _self_ .apply(this,arguments);
func.apply(this,arguments);
}
这样修改函数就变成了:(JQ的感觉对不对)
func = (func || function(){}).before(function(){
当然有人说这样污染了Function的原型,那自己写一个装饰函数,传入先后的函数:(未实际试验)
var fix = function(funobj){
funobj.before.apply(this,arguments);
var res = funobj.original.apply(this,arguments);
funobj.after.apply(this,arguments);
}
func = fix({original:(func||function(){}),
4.享元模式(对象池)
我们现在要做一个弹幕游戏……恩(或者做粒子效果=。=),或者我们有很多NPC都长一个样子,是不是载入图片就卡爆了呢,显然没有…ImageManager里面提供了解决的办法:
ImageManager._cache //图片信息的缓存
剩下的参看ImageManager的loadNormalBitmap函数即可
这种思路在使用大量相似对象、并且对象的大多数状态都一致,只是一些属性根据应用场景变化而变化
你们做一个MV的粒子效果试试,大概就是这样的模式。