设计模式
1.单例模式
/*有两种模式:第一种模式:对象直接量、第二种模式:使用闭包*/ /*singleton是一个全局变量,相当于C++中的一个类, 该函数采用闭包把result存储在内存中。*/ var singleton = function(fn) { var result; return function() { return result ||(result = fn.apply(this,arguments)); } } var createMask = singleton(function(){ return document.body.appendChild(document.createElement('div')); }) /*那么createMask这个变量引用就优先返回变量*/
原理:重写构造函数,在函数内部判断该函数是否有对象存在,如果有,返回,没有则构造。
优点:当一个变量赋值一次之后,以后调用不需要重新生成,在以后的调用中优先返回该变量。
缺点:始终需要一个变量result来存储div的引用,下面是比较复杂的单例模式:
var SingletonTester = (function(){ function Singleton(options){ options = options || {}; this.name = "SingletonTester"; this.pointX = options.pointX || 6; this.pointY = options.pointY || 10; } //闭包来储存单例变量 var instance; var _static = { name:"SingletonTester", getInstance:function(options){ if(instance === undefined){ instance = new Singleton(options); } return instance; } } return _static; //即时函数,返回一对象, })(); var singletonTest = SingletonTester.getInstance({ pointX:5 }); console.log(singletonTest.pointX);
vue工具函数中的cache也属于单例模式:
/** * Create a cached version of a pure function. */ export function cached<F: Function> (fn: F): F { const cache = Object.create(null) return (function cachedFn (str: string) { const hit = cache[str] return hit || (cache[str] = fn(str)) }: any) }
2.策略模式
策略模式又叫算法簇模式,将一组算法分装到一组具体共同接口的独立类或者对象中,它不影响客户端的情况下发生变化。
通常策略模式适用于当一个应用程序需要实现一种特点的服务和功能,而且该程序有多种实现方式时使用,其中一个例子是解决表单验证的问题。一般策略模式包含三个部分
1.环境对象:抽象策略对象的接口或者抽象类的引用。
2.抽象策略对象:一个一个的策略算法实现。
3.具体策略对象:分装不同策略的算法(核心)
我写了一个验证表单的策略算法:
//具体策略对象 var Validate = { types:{}, //环境对象 config:{}, message:[], validate:function(data,callback){ var i, msg, checker, result_ok,types; var flag = 0, msg; this.message = []; for(i in data){ flag = 0; msg = ""; types = this.config[i]; if(!types){ continue; } for(var j = 0; j < types.length; j++){ var type = types[j]; checker = this.types[type]; if(!checker){ throw { name : "ValidationError", message : "No handler to validate type" + type }; } result_ok = checker.validate(data[i]); console.log("result_ok = "+result_ok); if(!result_ok){ this.message.push(checker.instructions); msg = checker.instructions; flag = 1; break; } } console.log("i = "+i); console.log("msg = " + msg); if(flag){ callback(i, msg); } } return this.hasErrors(); }, hasErrors:function(){ return this.message.length !== 0; } } //策略算法实现 Validate.types.isEmail = { validate:function(emailStr){ var reg_email=/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/; return reg_email.test(emailStr); }, instructions:"输入邮箱不合法" } Validate.types.isPhone = { validate:function(phoneStr){ var reg_phone = /^((13[0-9])|(15[^4])|(18[0-9])|(17[0678])|(14[57]))\d{8}$/; return reg_phone.test(phoneStr); }, instructions:"输入电话号码不合法" } Validate.types.isNotEmpty = { validate:function(str){ return !!str.replace(/(^\s*)|(\s*$)/g,""); }, instructions:"不能为空" } //调用方式 //配置验证信息 Validate.config = { name: ['isNotEmpty'], // idcard:['isNotEmpty','checkCardId'], // mobile:['isNotEmpty','isPhone'], channel:['isNotEmpty'], company_name:['isNotEmpty'], // work_level:['isNotEmpty'], // work_years:['isNotEmpty','isNumber'], email:['isNotEmpty','isEmail'] }; Validate.message = []; Validate.validate(data,function(id,msg){ $("#"+id).parent('[name=hasError]').addClass('has-error'); $("#"+id+"_error_tips").text(msg).show(); }); if(!Validate.hasErrors()) { $.ajax({ url: that.cgi.addlist, type: 'POST', dataType: 'json', data: data }).done(function(result,xhr,status) { if(result.retCode === 0){ myUtils.showDialog("添加成功",result.retMsg,'type-info',BootstrapDialog.SIZE_SMALL,function(){ location.reload(); }); }else if(result.retCode == 10301){ location.href="/boss/admin/index.html"; }else{ myUtils.showDialog("添加失败",result.retMsg,'type-danger',BootstrapDialog.SIZE_SMALL); } }).fail(function(xhr,status) { myUtils.showDialog("添加失败","网络繁忙,请稍后再试!",'type-danger',BootstrapDialog.SIZE_SMALL); }); }
3.模块模式和沙箱模式
3.1模块模式
模块模式是非常常见的模式!它是以下几种模式的组合
- 命名空间
- 即时函数
- 私有和特权成员
- 声明依赖
该模式的第一步是建立一个命名空间。下一步是定义该模块,通过即时函数来定义,主要要平衡私有函数和对外接口,同时在即时函数上面,可以声明模块可能有依赖模块的位置。最终结果是一个由即时函数返回的对象,其中该对象包含了你模块的公共API。
MYAPP.utilities.example = (function(){ //依赖模块 var obj = MYAPP.utilities.object; //私有变量 var privateInt = 1; return { inArray:function(){ }, isArray:function(){ } }; }())
在常见的变化模式中,可以将参数传递到模块的即时函数中,导入全局变量有助于加速即时函数中的全局符号解析的速度,因为这些导入的变量成为了该函数的局部变量。
MYAPP.utilities.module = (function(app,global){ }(MYAPP,this)); //又如iscroll中整个代码都导入在一个声明依赖中, //nodejs中的模块设计吸取其中的不少优点。 (function (window, document, Math) { //源码 })(window, document, Math);
3.2 沙箱模式
JS的事件绑定采用的就是沙箱模式,它解决了模块模式的几个弊端。
1.模式模式比较有一个全局变量,它代表引用的该模块。
2.如果引用了两个不同版本的模块,那么会导致一个模块覆盖另一个模块。
3.对这种以点分隔的名称来说,需要输入更长的字符,并且在运行时需要解析更长的时间。
4 工厂模式
工厂模式是面向对象的设计模式,作用在于创建不同的对象类型。套路是先定义一个接口,让实现这个接口的类来决定实例化哪个类,也就是说通过一个函数把一个类型实例包装起来,这样可以通过调用函数来实现类型的实例化,具体也分成简单工厂模式和抽象工厂模式。
简单工厂模式要定义产品接口,和产品接口下的实现类,其次还要有一个Factory类,用来生成产品接口的具体实例,如:
//Factory类 var bikeFactory = { createBike : function(model){ var bike; switch(model){ case 'The Speedster': bike = new Speedster(); break; case 'The Lowerster': bike = new Lowerster(); break; case "Other": default: bike = new Other(); break; } return bike; } } var BicycleShop = function(){}; //让实现这个接口的类来决定实例化哪个类 BicycleShop.prototype = { sellPrototype:function(model){ //调用Factory var bicycle = bikeFactory.createBike(model); return bicycle; } }
5.订阅和发布者模式
VueJS的数据到视图设计就是订阅和发布者模式:当数据变化了,就会更新所有订阅该数据变化的节点。重点在于:让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象,
5.1如何设计这个主题对象。代码如下:
function Dep(){ this.subs = [];
} Dep.prototype = { addSub:function(sub){ //订阅 this.subs.push(sub); }, notify:function(){ //发布 this.subs.forEach(function(sub){ sub.update(); }); } }
5.2 如何订阅和发布主体:
function defineReactive(obj,key,val){ var dep = new Dep(); Object.defineProperty(obj,key,{ get:function(){ if(Dep.target) dep.addSub(Dep.target); return val; }, set:function(newVal){ if(newVal == val) return; val = newVal; console.log(val); dep.notify(); } }) }
5.3 如何关联数据变化:
function Watch(vm,node,name){ Dep.target = this; this.name = name; this.node = node; this.vm = vm; this.update(); Dep.target = null; } Watch.prototype = { update:function(){ this.get(); this.node.nodeValue = this.value; }, get:function(){ this.value = this.vm[this.name]; //读取了 vm 的访问器属性,触发get } }
当html解析到节点,给需求进行数据同步的文本节点注册watch对象。该对象执行.update(); -->get()赋值--> 完成订阅--->重新把Dep.target = null。
发布:
node.addEventListener("input",function(e){ vm[name] = e.target.value; //输入的时候出的set时间。 });