再起航,我的学习笔记之JavaScript设计模式27(链模式)

链模式

概念介绍

链模式(Operatc of Responsibility):
通过在对象方法中将当前对象返回,实现对同一个对象多个方法的链式调用。从而简化对该对象的多个方法的多次调用时,对该对象的多次引用。

原型式继承

链模式顾名思义就是像链子一样一个接一个的,我们可以通过点语法在一个方法的后面接着调用另一个方法,那么这种模式是怎么做到的呢?一般来说链模式是基于原型继承的,并且在每一个原型方法的实现上都返回当前对象this,使当前对象一直处于原型链作用域的顶端。这样我们就可以实现链式调用
我们创建一个对象A,并且给A的原型对象上添加一个length属性,和size方法,如果我们要调用的话就要通过new来创建新对象访问

var A=function(){}
A.prototypee={
	length:2,
	size:function(){
		return this.length;
	}
}
var a=new A();
console.log(a.size());

再起航,我的学习笔记之JavaScript设计模式27(链模式)

我们看到这样调用我们可以正常访问,但是下面两种方式去访问程序就会报错

console.log(A.size())

再起航,我的学习笔记之JavaScript设计模式27(链模式)

因为size绑定在A的原型上没有绑定在其自身上。

console.log(A().size())

再起航,我的学习笔记之JavaScript设计模式27(链模式)

因为A函数对象执行的结果没有返回值所以找不到size方法
上述两种方式都是因为size方法绑定在A类的原型上导致的。那么我们如果能让其访问呢?
我们可以借助另一个对象来实现。

var A=function(){
	return B;
}
var B=A.prototypee={
	length:2,
	size:function(){
		return this.length;
	}
}

我们再来看看

console.log(A().size());

再起航,我的学习笔记之JavaScript设计模式27(链模式)

获取元素

我们看到现在这个A().size()的形态是不是就有链式结构的雏形了,如果这个A()能获取对象该多好,那我们接着往下面拓展功能

var A=function(){
	return A.fn;
}

A.fn=A.prototypee={}

var A=function(selector){
	return A.fn.init(selector);
}


A.fn=A.prototype={
	init:function(selector){
		return document.getElementById(selector)
	},
	length:2,
	size:function(){
		return this.length;
	}
}


console.log(A('demo'));

再起航,我的学习笔记之JavaScript设计模式27(链模式)

现在我们已经能获取到元素了,但是如果想一级一级的去调用,我们还要让A对象返回的结果能拥有A.fn中的方法,这个时候我们就可以通过返回this来达到我们的目的

var A=function(selector){
	return A.fn.init(selector);
}
A.fn=A.prototype={
	init:function(selector){
		//作为当前对象的属性值保存
		this[0]=document.getElementById(selector);
		//校正length属性
		this.length=1;
		//返回当前对象
		return this;
	},
	length:2,
	size:function(){
		return this.length;
	}
}

我们来测试一下

var demo=A('demo');
console.log(demo);
console.log(A('demo').size());

再起航,我的学习笔记之JavaScript设计模式27(链模式)

如果想把结果像数组那样访问,我们可以将他们的属性值顺序地设置为数字索引。为了更像数组我们还校正了它的length属性

但我们这样做有个弊端,就是后面获取的对象会把我们前面的对象覆盖掉

var test=A('container');
console.log(demo);

再起航,我的学习笔记之JavaScript设计模式27(链模式)

出现这种情况的原因是因为每次在A的构造函数中返回的A.fn.init(selector)对象都指向同一个对象造成的,我们直接使用new来创建即可

var A=function(selector){
	return new A.fn.init(selector);
}
console.log(A('demo'))
console.log(A('container'))
console.log(A('demo').size())

我们发现虽然我们解决了元素覆盖的问题,但是我们那种链式的写法好像也失效了,为什么会出现这种情况呢?
这是因为A.fn.init(selector)与new A.fn.init(selector)的实现的差别造成的,牵着返回的this指向当前的对象,而后者用new对对象内的属性进行了复制,所以this指向的就不当前对象了,我们来测试说明一下。

init:function(selector){
	this[0]=document.getElementById(selector);
	this.length=1;
	console.log(this===A.fn,this===A.prototypee,this);
	return this;
}

再起航,我的学习笔记之JavaScript设计模式27(链模式)

A.fn.init(selector)返回结果如下
再起航,我的学习笔记之JavaScript设计模式27(链模式)

new A.fn.init(selector)返回结果如下
再起航,我的学习笔记之JavaScript设计模式27(链模式)

那么我们如何去解决这个问题呢,其实只用将构造函数的原型指向一个已存在的对象即可
A.fn.init.prototype=A.fn;

我们再试试

var A=function(selector){
	return new A.fn.init(selector);
}
A.fn=A.prototype={
	init:function(selector){
	this[0]=document.getElementById(selector);
	this.length=1;
	return this;
	},
	length:2,
	size:function(){
		return this.length;
	}
}
A.fn.init.prototype=A.fn;

console.log(A('demo').size())

再起航,我的学习笔记之JavaScript设计模式27(链模式)

好了现在我们可以接着像之前那样调用了

方法拓展

那么现在我们也能获取到对象了也能调用方法了,那我们怎么通过点语法链式使用呢,我们又要如何添加呢?

//对象拓展
A.extend=A.fn.extend=function(){
	//拓展对象从第二个参数算起
	var i=1,
	//获取参数长度
	len=arguments.length,
	//第一个参数为源对象
	target=arguments[0],
	//拓展对象中属性
	j;
	//如果只穿一个参数
	if(i==len){
		//源对象为当前对象
		target=this;
		//i从0计数
		i--;
	}
	//遍历参数中拓展对象
	for(;i<len;i++){
		//遍历拓展对象中的属性
		for(j in arguments[i]){
		//拓展源对象
		target[j]=arguments[i][j];
		}
	}
	//返回源对象
	return target;
}

我们来调用试试

var demo=A.extend({first:1},{second:2},{third:3});
console.log(demo);
A.extend(A.fn,{version:'1.0'});
console.log(A('demo').version);
A.fn.extend({getVersion:function(){return this.version}})
console.log(A('demo').getVersion());
A.extend(A,{names:'李四'});
console.log(A.names);

再起航,我的学习笔记之JavaScript设计模式27(链模式)

实现链式调用

A.extend({
	//分割带“-”样式,变为驼峰式写法
	camelCase:function(str){
		return str.replace(/-(w)/g,function(all,letter){
			return letter.toUpperCase();
		});
	}
});

A.fn.extend({
	//设置css样式
	css:function(){
		var arg=arguments,
		len=arg.length;
		if(this.length<1){
			return this;
		}
		//只有一个参数时
		if(len===1){
			//如果为字符串则为获取一个元素CSS样式
			if(typeof arg[0]==='string'){
				//IE
				if (this[0].currentStyle) {
					return this[0].currentStyle[name];
				} else{
					return getComputedStyle(this[0],false)[name];
				}
			//为对象时则设置多个样式
			}else if(typeof arg[0]==='object'){
				//遍历每个样式
				for (var i in arg[0]) {
					for(var j=this.length-1;j>=0;j--){
						//分割-为驼峰式写法
						this[j].style[A.camelCase(i)]=arg[0][i];
					}
				}
			}
			//两个参数则设置一个样式
		}else if(len===2){
			for (var j=this.length-1;j>=0;j--) {
				this[j].style[A.camelCase(arg[0])]=arg[1];
			}
		}
		return this ;
	}
})

A.fn.extend({
	//设置属性
	attr:function(){
		var arg=arguments,
		len=arg.length;
		if(this.length<1){
			return this;
		}
		//如果一个参数
		if (len===1) {
			//为字符串获取第一个元素属性
			if(typeof arg[0]==='string'){
				return this[0].getAttribute(arg[0]);
			}
			//为对象设置每个元素的多个属性
			else if(typeof arg[0]==='object'){
				//遍历属性
			for (var i in arg[0]) {
				for (var j=this.length-1;j>=0;j--) {
					this[j].setAttribute(i,arg[0][i]);
				}
			}
		}
			//两个参数则设置每个元素单个属性
		}else if(len===2){
	
				for (var j=this.length-1;j>=0;j--) {
					this[j].setAttribute(arg[0],arg[i]);
				}
			
		}
		return this;
	}
})

A.fn.extend({
	//获取或设置元素的内容
	html:function(){
		var arg=arguments,
		len=arg.length;
		//如果没参数则取第一个元素的内容
		if (len===0) {
			return this[0]&&this[0].innerHTML;
		}else{
			//一个参数则设置每个元素的内容
			for (var i=this.length-1;i>=0;i--) {
				this[i].innerHTML=arg[0];
			}
		}
		return this;
	}
})
A.fn.extend({
	//添加时间
	on:(function(){
		//如果支持DOM2级事件
		if(document.addEventListener){
			return function(type,fn){
				var i=this.length-1;
				for(;i>=0;i--){
					this[i].addEventListener(type,fn,false);
				}
				return this;
			}
			//IE浏览器DOM2级事件
		}else if(document.attachEvent){
			return function(type,fn){
				var i=this.length-1;
				for (;i>=0;i--) {
					this[i].addEvent('on'+type,fn);
				}
				return this;
			}
			//不支持DOM2级浏览器添加事件
		}else{
			return function(type,fn){
				var i=this.length-1;
				for(;i>=0;i--){
					this[i]['on'+type]=fn;
				}
				return this;
			}
		}
	})()
})

我们来看一下效果

A('demo').css({
	height:'30px',
	borer:'1px solid #000',
	'background-color':'red'
})
.attr('class','demo')
.html('添加文字')
.on('click',function(){
	console.log('触发点击事件');
});

再起航,我的学习笔记之JavaScript设计模式27(链模式)

总结

JavaScript中的链模式的核心思想就是通过在对象中的每个方法调用执行完毕后返回当前对象this来实现,由于链模式使得代码紧凑简洁而高效,目前的主流代码库都已该模式作为一种风格,比如我们最熟悉的jQuery。

也谢谢大家看到这里:)如果你觉得我的分享还可以请点击推荐,分享给你的朋友让我们一起进步~

好了以上就是本次分享的全部内容,本次示例参考自JavaScript设计模式一书,让我们一点点积累一点点成长,希望对大家有所帮助。

欢迎转载,转载请注明作者,原文出处。