javascript设计模式1
设计模式,不得不先重点着墨于“接口设计”,因为接口设计在设计模式中的意义太大了,大于模式本身。直观起见,先介绍一下接口定义的形式:
可以看出接口函数必须包含两个参数,接口方法定义在一个二维数组中。上例中定义了两个接口方法:getName,getAge,这两个方法都带一个参数,下面我们详细看一下Interface函数的实现代码,从而加深大家对接口的理解。
从代码中不难看出,接口函数的定义规则:[1]Interface函数只能包含两个参数,第一个参数为接口名称,第二个参数是一个二维数组[2]第二个参数不允许为空数组[3]methods参数中的第一个参数必须为字符串类型,用以定义方法名,第二个参数必须为整数类型,用以定义方法的参数个数 [4]当methods中方法的参数个数为0时,可以省略不写。
接下来要建一个类,让该类继承前面定义的interface接口,那该怎么做呢,别急,我们需要新增一个方法,见如下代码:
Interface.regImplement就是我们要新增的方法,作用就是让ioldfish类按照接口interface的规范编码,否则将会在firebug的控制台抛出异常。
看看这个方法的具体实现代码:
解读这段代码,你很容易发现:[1]Interface.regImplement继承接口函数的参数至少要有两个,如果有第三个参数,那么该参数必须是Interface接口的实例[2]我们去遍历interface接口中的方法,再与新增类中的方法一一匹配,如果发现继承了该接口规范的类缺少某方法,就会抛出错误提示。[3]接口对于参数个数也进行了匹配,如果接口方法中的参数个数与新增类中方法的个数不匹配也会抛出错误提示。
为了匹配方法中参数个数,这里用到一个getParameters()方法,我们基于Function做个扩展,代码实现如下:
接下来,你可以把所讲的Interface函数,Interface.regImplement 函数,还有Function.prototype.getParameters函数整合到一个interface.js的文件中,调试一下新建的这个 ioldfish类。看看当类中缺少getAge方法时会怎么样?建议新手,各类情况都模拟一下,加强理解吧!如果你确信已经完全理解接口设计,那就跟着我继续往下走。
Javascript设计模式之单体模式Singleton
单体模式Singleton:这是最基础的设计模式,严格来说没什么模式可言,但是却很容易用也很好用,支付宝很多组件都是通过单体模式设计的。事实上在《浅谈javascript面向对象编程》中阐述原型继承的时候就已经用到了该模式,这里简单带过,重点说一下惰性单体,这对一些不是所有用户都需要,在特定情景下才会用到的组件有非常好的优化作用,他可以让组件的实例化推迟到用户触发他的时候。
上例是一个最简单的单体模式,把本人的资料都整合到ioldfish这个对象字面量中,形成一个模块,同时起到了一个命名空间的作用。
对第一个单体做简单的修改,通过闭包让name,age成为静态私有变量,确保实例化的时候在内存中始终只有一份,这样更符合单体模式的定义。
下面重点介绍一下惰性单体,废话少说,先看看我们该如何来实现惰性单体:
上面的结构公私分明一目了然,私有变量uniqueInstance(标识类是否已经实例化)和私有方法constructor,返回一个公有方法 isInstance(通过该方法可以调用私有方法constructor中定义的方法),形如:ioldfish.isInstance().getName();先通过isInstance()方法判断其是否被实例化,然后通过 getName()方法获取到闭包内的私有变量name。该模式的应用场景还是很多的,是不是遇
Javascript设计模式之工厂模式Factory
工厂模式Factory:先创建一个抽象类,然后基于这个抽象类派生出子类,并在子类中创建工厂方法,从而把实例化推迟到对应的子类中进行,说实话,工厂模式在javascript中的应用有些牵强,毕竟javascript不像java存在硬编码带来的困搅,要学习的只是模式的思想,切忌因为模式而模式。
不妨举个偏激点的例子,为tab切换、下拉列表等组件添加定位,渐隐,延迟等效果,我们可以先为这些组件定义一个接口:
定义该接口,以便之后派生的子类继承,接口中定义了一个addEffect方法,接口方法实现后,调用的同学大可不必关注各子类中对于addEffect方法的代码实现。
上例先定义一个抽象类Widget,做为派生子类的父类,由于考虑到这两类组件都涉及到隐藏和显示一个容器,所以在父类中预先定义好show和hide方法以便子类继承。
子类xTab和dropDown继承了父类,并且重写了createWidget方法,不同的子类根据定位,渐隐,延迟效果分别创建不同的实例,只要创建这些实例的类都实现接口中约定的addEffect方法,至于方法代码如何实现,千篇一律,爱咋整咋整。
以此类推,如果您需要为气泡组件添加这些效果,照葫芦画瓢就可以了,说到这里你可以清楚的看到,这种设计模式大大降低了类和类之间的耦合度,而且可以根据具体的交互需求,实现不同的辅助动作,但是也无可避免的增加了代码实现上的复杂性,事实上这种模式并不适合javascript,毕竟它有别于java,不会有类名硬编码的问题,目的是学习他的设计思想,所以以上示例仅供参考,如无大人在旁,小朋友切勿效仿。
对于javascript爱好者来说,更有价值的应该是工厂模式中讲到的的”缓存(memoization)机制”,书上举了个创建XHR对象的例子来说明该特性,但是效果显然不够明显……
memoization名词解释:把函数的每次执行结果都放入一个键值对(数组也可以,视情况而定)中,在接下来的执行中,在键值对中查找是否已经有相应执行过的值,如果有,直接返回该值,没有才 真正执行函数体的求值部分。很明显,找值,尤其是在键值对中找值,比执行函数快多了
在递归调用的时候,memoization的威力才能更好的显现。下面是一个经典的斐波纳契序列,fib(20) 会把fib这个方法执行21891次,如果是fib(40),这会执行331160281次。
再看看如何使用memoization来实现:
将Function的原型扩展memoize 和unmemoize 方法,这样你可以对任何函数实现memoize和解除memoize,当然,这个方法要慎,对一些不是频繁执行的函数,没必要缓存:
使用方法:fib.memoize();
来源:http://hi.baidu.com/zhoumm1008/blog/item/483b1729271fabfb8b1399a1.html
+展开
-JavaScript
var interface = new Interface("interface",[["getName",1],["getAge",1]]);
可以看出接口函数必须包含两个参数,接口方法定义在一个二维数组中。上例中定义了两个接口方法:getName,getAge,这两个方法都带一个参数,下面我们详细看一下Interface函数的实现代码,从而加深大家对接口的理解。
+展开
-JavaScript
function Interface(name,methods){
if(arguments.length !=2){
console.log("参数必须为二个");
}
this.name = name;
this.methods = [];
if(methods.length<1){
console.log("第二个参数不能为空数组");
}
for(var i=0;len=methods.length,i<len;i++){
if(typeof methods[i][0] !== 'string'){
console.log("第一个参数数据类型必须为字符串");
}
if(methods[i][1] && typeof methods[i][1] !== 'number'){
console.log("第二个参数数据类型必须为整数型");
}
if(methods[i].length == 1){
methods[i][1] = 0;
}
this.methods.push(methods[i]);
}
}
if(arguments.length !=2){
console.log("参数必须为二个");
}
this.name = name;
this.methods = [];
if(methods.length<1){
console.log("第二个参数不能为空数组");
}
for(var i=0;len=methods.length,i<len;i++){
if(typeof methods[i][0] !== 'string'){
console.log("第一个参数数据类型必须为字符串");
}
if(methods[i][1] && typeof methods[i][1] !== 'number'){
console.log("第二个参数数据类型必须为整数型");
}
if(methods[i].length == 1){
methods[i][1] = 0;
}
this.methods.push(methods[i]);
}
}
从代码中不难看出,接口函数的定义规则:[1]Interface函数只能包含两个参数,第一个参数为接口名称,第二个参数是一个二维数组[2]第二个参数不允许为空数组[3]methods参数中的第一个参数必须为字符串类型,用以定义方法名,第二个参数必须为整数类型,用以定义方法的参数个数 [4]当methods中方法的参数个数为0时,可以省略不写。
接下来要建一个类,让该类继承前面定义的interface接口,那该怎么做呢,别急,我们需要新增一个方法,见如下代码:
+展开
-JavaScript
var ioldfish = function(name,age){
this.name = name;
this.age = age;
Interface.regImplement(this,interface);
}
ioldfish.prototype.getName = function(){
alert(this.name);
};
ioldfish.prototype.getAge = function(){
alert(this.age);
};
var fishwl = new ioldfish("老鱼",27);
fishwl.getName();
this.name = name;
this.age = age;
Interface.regImplement(this,interface);
}
ioldfish.prototype.getName = function(){
alert(this.name);
};
ioldfish.prototype.getAge = function(){
alert(this.age);
};
var fishwl = new ioldfish("老鱼",27);
fishwl.getName();
Interface.regImplement就是我们要新增的方法,作用就是让ioldfish类按照接口interface的规范编码,否则将会在firebug的控制台抛出异常。
看看这个方法的具体实现代码:
+展开
-JavaScript
Interface.regImplement = function(object){
if(arguments.length<2){
console.log("接口继承参数不能少于二个");
}
for(var i=1;len = arguments.length,i<len;i++){
var interface = arguments[i];
if(interface.constructor !== Interface){
console.log("第三个参数开始必须为接口实例");
}
for(var j=0;len=interface.methods.length,j<len;j++){
var method = interface.methods[j][0];
if(!object[method] || typeof object[method] !=="function" || object[method].getParameters().length !== interface.methods[j][1]){
console.log(""+method+"方法接口不匹配");
}
}
}
}
if(arguments.length<2){
console.log("接口继承参数不能少于二个");
}
for(var i=1;len = arguments.length,i<len;i++){
var interface = arguments[i];
if(interface.constructor !== Interface){
console.log("第三个参数开始必须为接口实例");
}
for(var j=0;len=interface.methods.length,j<len;j++){
var method = interface.methods[j][0];
if(!object[method] || typeof object[method] !=="function" || object[method].getParameters().length !== interface.methods[j][1]){
console.log(""+method+"方法接口不匹配");
}
}
}
}
解读这段代码,你很容易发现:[1]Interface.regImplement继承接口函数的参数至少要有两个,如果有第三个参数,那么该参数必须是Interface接口的实例[2]我们去遍历interface接口中的方法,再与新增类中的方法一一匹配,如果发现继承了该接口规范的类缺少某方法,就会抛出错误提示。[3]接口对于参数个数也进行了匹配,如果接口方法中的参数个数与新增类中方法的个数不匹配也会抛出错误提示。
为了匹配方法中参数个数,这里用到一个getParameters()方法,我们基于Function做个扩展,代码实现如下:
+展开
-JavaScript
Function.prototype.getParameters = function(){
var str = this.toString();
var paramStr = str.slice(str.indexOf("(")+1,str.indexOf(")")).replace(/\s*/g,'');
try{
return (paramStr.length ==0 ? [] : paramStr.split(","));
}
catch(err){
console.log("非法函数");
}
}
var str = this.toString();
var paramStr = str.slice(str.indexOf("(")+1,str.indexOf(")")).replace(/\s*/g,'');
try{
return (paramStr.length ==0 ? [] : paramStr.split(","));
}
catch(err){
console.log("非法函数");
}
}
接下来,你可以把所讲的Interface函数,Interface.regImplement 函数,还有Function.prototype.getParameters函数整合到一个interface.js的文件中,调试一下新建的这个 ioldfish类。看看当类中缺少getAge方法时会怎么样?建议新手,各类情况都模拟一下,加强理解吧!如果你确信已经完全理解接口设计,那就跟着我继续往下走。
Javascript设计模式之单体模式Singleton
单体模式Singleton:这是最基础的设计模式,严格来说没什么模式可言,但是却很容易用也很好用,支付宝很多组件都是通过单体模式设计的。事实上在《浅谈javascript面向对象编程》中阐述原型继承的时候就已经用到了该模式,这里简单带过,重点说一下惰性单体,这对一些不是所有用户都需要,在特定情景下才会用到的组件有非常好的优化作用,他可以让组件的实例化推迟到用户触发他的时候。
+展开
-JavaScript
var ioldfish = {
name:'老鱼',
age:27,
getName:function(){
alert(name);
},
getAge:function(){
alert(age);
}
}
name:'老鱼',
age:27,
getName:function(){
alert(name);
},
getAge:function(){
alert(age);
}
}
上例是一个最简单的单体模式,把本人的资料都整合到ioldfish这个对象字面量中,形成一个模块,同时起到了一个命名空间的作用。
+展开
-JavaScript
var ioldfish =(function(){
var name = '老鱼';
var age = 27;
return{
getName:function(){
alert(name);
},
getAge:function(){
alert(age);
}
}
})();
var name = '老鱼';
var age = 27;
return{
getName:function(){
alert(name);
},
getAge:function(){
alert(age);
}
}
})();
对第一个单体做简单的修改,通过闭包让name,age成为静态私有变量,确保实例化的时候在内存中始终只有一份,这样更符合单体模式的定义。
下面重点介绍一下惰性单体,废话少说,先看看我们该如何来实现惰性单体:
+展开
-JavaScript
var ioldfish = (function(){
var uniqueInstance;
var name = '老鱼';
var age = 27;
function constructor(){
return{
getName:function(){
alert(name);
},
getAge:function(){
alert(age);
}
}
}
return{
isInstance:function(){
if(uniqueInstance == null){
uniqueInstance = constructor();
}
return uniqueInstance;
}
}
})();
ioldfish.isInstance().getName();
var uniqueInstance;
var name = '老鱼';
var age = 27;
function constructor(){
return{
getName:function(){
alert(name);
},
getAge:function(){
alert(age);
}
}
}
return{
isInstance:function(){
if(uniqueInstance == null){
uniqueInstance = constructor();
}
return uniqueInstance;
}
}
})();
ioldfish.isInstance().getName();
上面的结构公私分明一目了然,私有变量uniqueInstance(标识类是否已经实例化)和私有方法constructor,返回一个公有方法 isInstance(通过该方法可以调用私有方法constructor中定义的方法),形如:ioldfish.isInstance().getName();先通过isInstance()方法判断其是否被实例化,然后通过 getName()方法获取到闭包内的私有变量name。该模式的应用场景还是很多的,是不是遇
Javascript设计模式之工厂模式Factory
工厂模式Factory:先创建一个抽象类,然后基于这个抽象类派生出子类,并在子类中创建工厂方法,从而把实例化推迟到对应的子类中进行,说实话,工厂模式在javascript中的应用有些牵强,毕竟javascript不像java存在硬编码带来的困搅,要学习的只是模式的思想,切忌因为模式而模式。
不妨举个偏激点的例子,为tab切换、下拉列表等组件添加定位,渐隐,延迟等效果,我们可以先为这些组件定义一个接口:
+展开
-JavaScript
var Iwidget = new Interface("iwidget",[["addEffect"]]);
定义该接口,以便之后派生的子类继承,接口中定义了一个addEffect方法,接口方法实现后,调用的同学大可不必关注各子类中对于addEffect方法的代码实现。
+展开
-JavaScript
var Widget = function(){};
Widget.prototype={
fire:function(model){
var widget = this.createWidget(model);
//有同学问为什么子类都必须定义接口方法,因为下面要调用嘛
widget.addEffect();
return widget;
},
show:function(){
//show代码具体实现
},
hide:function(){
//hide代码具体实现
},
createWidget:function(model){
alert('抽象类,不可以实例化')
}
};
Widget.prototype={
fire:function(model){
var widget = this.createWidget(model);
//有同学问为什么子类都必须定义接口方法,因为下面要调用嘛
widget.addEffect();
return widget;
},
show:function(){
//show代码具体实现
},
hide:function(){
//hide代码具体实现
},
createWidget:function(model){
alert('抽象类,不可以实例化')
}
};
上例先定义一个抽象类Widget,做为派生子类的父类,由于考虑到这两类组件都涉及到隐藏和显示一个容器,所以在父类中预先定义好show和hide方法以便子类继承。
+展开
-JavaScript
var xTab = function(){};
extend(xTab,Widget);
xTab.prototype.createWidget = function(model){
var widget;
switch(model){
case 'position':
widget = new xTabPosition();
break;
case 'anim':
widget = new xTabAnim();
break;
case 'delay':
default:
widget = new xTabDelay();
}
};
var dropDown = function(){};
extend(dropDown,Widget);
dropDown.prototype.createWidget = function(model){
var widget;
switch(model){
case 'position':
widget = new dropDownPosition();
break;
case 'anim':
widget = new dropDownAnim();
break;
case 'delay':
default:
widget = new dropDownDelay();
}
};
extend(xTab,Widget);
xTab.prototype.createWidget = function(model){
var widget;
switch(model){
case 'position':
widget = new xTabPosition();
break;
case 'anim':
widget = new xTabAnim();
break;
case 'delay':
default:
widget = new xTabDelay();
}
};
var dropDown = function(){};
extend(dropDown,Widget);
dropDown.prototype.createWidget = function(model){
var widget;
switch(model){
case 'position':
widget = new dropDownPosition();
break;
case 'anim':
widget = new dropDownAnim();
break;
case 'delay':
default:
widget = new dropDownDelay();
}
};
子类xTab和dropDown继承了父类,并且重写了createWidget方法,不同的子类根据定位,渐隐,延迟效果分别创建不同的实例,只要创建这些实例的类都实现接口中约定的addEffect方法,至于方法代码如何实现,千篇一律,爱咋整咋整。
+展开
-JavaScript
var xTabPosition = function(){};
xTabPosition.prototype ={
addEffect:function(){
//具体实现代码
}
};
var dropDownPosition = function(){};
dropDownPosition.prototype ={
addEffect:function(){
//具体实现代码
}
};
var dropDownInstance = new dropDown();
dropDownInstance.fire('position');
xTabPosition.prototype ={
addEffect:function(){
//具体实现代码
}
};
var dropDownPosition = function(){};
dropDownPosition.prototype ={
addEffect:function(){
//具体实现代码
}
};
var dropDownInstance = new dropDown();
dropDownInstance.fire('position');
以此类推,如果您需要为气泡组件添加这些效果,照葫芦画瓢就可以了,说到这里你可以清楚的看到,这种设计模式大大降低了类和类之间的耦合度,而且可以根据具体的交互需求,实现不同的辅助动作,但是也无可避免的增加了代码实现上的复杂性,事实上这种模式并不适合javascript,毕竟它有别于java,不会有类名硬编码的问题,目的是学习他的设计思想,所以以上示例仅供参考,如无大人在旁,小朋友切勿效仿。
对于javascript爱好者来说,更有价值的应该是工厂模式中讲到的的”缓存(memoization)机制”,书上举了个创建XHR对象的例子来说明该特性,但是效果显然不够明显……
memoization名词解释:把函数的每次执行结果都放入一个键值对(数组也可以,视情况而定)中,在接下来的执行中,在键值对中查找是否已经有相应执行过的值,如果有,直接返回该值,没有才 真正执行函数体的求值部分。很明显,找值,尤其是在键值对中找值,比执行函数快多了
在递归调用的时候,memoization的威力才能更好的显现。下面是一个经典的斐波纳契序列,fib(20) 会把fib这个方法执行21891次,如果是fib(40),这会执行331160281次。
+展开
-JavaScript
function fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
再看看如何使用memoization来实现:
+展开
-JavaScript
var iterMemoFib = (function() {
var cache = [1, 1];
var fib = function(n) {
if (n >= cache.length) {
//将一个递归转换成了一个
for (var i = cache.length; i <= n; i++) {
cache[i] = cache[i - 2] + cache[i - 1];
}
}
return cache[n-1];
}
return fib;
})();
var cache = [1, 1];
var fib = function(n) {
if (n >= cache.length) {
//将一个递归转换成了一个
for (var i = cache.length; i <= n; i++) {
cache[i] = cache[i - 2] + cache[i - 1];
}
}
return cache[n-1];
}
return fib;
})();
将Function的原型扩展memoize 和unmemoize 方法,这样你可以对任何函数实现memoize和解除memoize,当然,这个方法要慎,对一些不是频繁执行的函数,没必要缓存:
+展开
-JavaScript
Function.prototype.memoize = function() {
var pad = {};
var self = this;
var obj = arguments.length > 0 ? arguments[i] : null;
var memoizedFn = function() {
// 把参数作为数组保存,作为键,把函数执行的结果作为值缓存起来
var args = [];
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
if (!(args in pad)) {
pad[args] = self.apply(obj, arguments);
}
return pad[args];
}
memoizedFn.unmemoize = function() {
return self;
}
return memoizedFn;
}
Function.prototype.unmemoize = function() {
alert("Attempt to unmemoize an unmemoized function.");
return null;
}
var pad = {};
var self = this;
var obj = arguments.length > 0 ? arguments[i] : null;
var memoizedFn = function() {
// 把参数作为数组保存,作为键,把函数执行的结果作为值缓存起来
var args = [];
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
if (!(args in pad)) {
pad[args] = self.apply(obj, arguments);
}
return pad[args];
}
memoizedFn.unmemoize = function() {
return self;
}
return memoizedFn;
}
Function.prototype.unmemoize = function() {
alert("Attempt to unmemoize an unmemoized function.");
return null;
}
使用方法:fib.memoize();
来源:http://hi.baidu.com/zhoumm1008/blog/item/483b1729271fabfb8b1399a1.html
加支付宝好友偷能量挖...