您的位置:首页 > Web前端 > JavaScript

深入理解JavaScript:代码复用模式(推荐篇)

2012-12-02 09:04 549 查看

介绍

本文介绍的四种代码复用模式都是最佳实践,推荐大家在编程的过程中使用。

模式1:原型继承

原型继承是让父对象作为子对象的原型,从而达到继承的目的:

functionobject(o){
functionF(){
}

F.prototype=o;
returnnewF();
}

//要继承的父对象
varparent={
name:"Papa"
};

//新对象
varchild=object(parent);

//测试
console.log(child.name);//"Papa"

//父构造函数
functionPerson(){
//an"own"property
this.name="Adam";
}
//给原型添加新属性
Person.prototype.getName=function(){
returnthis.name;
};
//创建新person
varpapa=newPerson();
//继承
varkid=object(papa);
console.log(kid.getName());//"Adam"

//父构造函数
functionPerson(){
//an"own"property
this.name="Adam";
}
//给原型添加新属性
Person.prototype.getName=function(){
returnthis.name;
};
//继承
varkid=object(Person.prototype);
console.log(typeofkid.getName);//"function",因为是在原型里定义的
console.log(typeofkid.name);//"undefined",因为只继承了原型


同时,ECMAScript5也提供了类似的一个方法叫做Object.create用于继承对象,用法如下:

/*使用新版的ECMAScript5提供的功能*/
varchild=Object.create(parent);

varchild=Object.create(parent,{
age:{value:2}//ECMA5descriptor
});
console.log(child.hasOwnProperty("age"));//true


而且,也可以更细粒度地在第二个参数上定义属性:

//首先,定义一个新对象man
varman=Object.create(null);

//接着,创建包含属性的配置设置
//属性设置为可写,可枚举,可配置
varconfig={
writable:true,
enumerable:true,
configurable:true
};

//通常使用Object.defineProperty()来添加新属性(ECMAScript5支持)
//现在,为了方便,我们自定义一个封装函数
vardefineProp=function(obj,key,value){
config.value=value;
Object.defineProperty(obj,key,config);
}

defineProp(man,'car','Delorean');
defineProp(man,'dob','1981');
defineProp(man,'beard',false);


所以,继承就这么可以做了:

vardriver=Object.create(man);
defineProp(driver,'topSpeed','100mph');
driver.topSpeed//100mph


但是有个地方需要注意,就是Object.create(null)创建的对象的原型为undefined,也就是没有toString和valueOf方法,所以alert(man);的时候会出错,但alert(man.car);是没问题的。

模式2:复制所有属性进行继承

这种方式的继承就是将父对象里所有的属性都复制到子对象上,一般子对象可以使用父对象的数据。

先来看一个浅拷贝的例子:

/*浅拷贝*/
functionextend(parent,child){
vari;
child=child||{};
for(iinparent){
if(parent.hasOwnProperty(i)){
child[i]=parent[i];
}
}
returnchild;
}

vardad={name:"Adam"};
varkid=extend(dad);
console.log(kid.name);//"Adam"

vardad={
counts:[1,2,3],
reads:{paper:true}
};
varkid=extend(dad);
kid.counts.push(4);
console.log(dad.counts.toString());//"1,2,3,4"
console.log(dad.reads===kid.reads);//true


代码的最后一行,你可以发现dad和kid的reads是一样的,也就是他们使用的是同一个引用,这也就是浅拷贝带来的问题。

我们再来看一下深拷贝:

/*深拷贝*/
functionextendDeep(parent,child){
vari,
toStr=Object.prototype.toString,
astr="[objectArray]";

child=child||{};

for(iinparent){
if(parent.hasOwnProperty(i)){
if(typeofparent[i]==='object'){
child[i]=(toStr.call(parent[i])===astr)?[]:{};
extendDeep(parent[i],child[i]);
}else{
child[i]=parent[i];
}
}
}
returnchild;
}

vardad={
counts:[1,2,3],
reads:{paper:true}
};
varkid=extendDeep(dad);

kid.counts.push(4);
console.log(kid.counts.toString());//"1,2,3,4"
console.log(dad.counts.toString());//"1,2,3"

console.log(dad.reads===kid.reads);//false
kid.reads.paper=false;


深拷贝以后,两个值就不相等了,bingo!

模式3:混合(mix-in)

混入就是将一个对象的一个或多个(或全部)属性(或方法)复制到另外一个对象,我们举一个例子:

functionmix(){
vararg,prop,child={};
for(arg=0;arg<arguments.length;arg+=1){
for(propinarguments[arg]){
if(arguments[arg].hasOwnProperty(prop)){
child[prop]=arguments[arg][prop];
}
}
}
returnchild;
}

varcake=mix(
{eggs:2,large:true},
{butter:1,salted:true},
{flour:'3cups'},
{sugar:'sure!'}
);

console.dir(cake);


mix函数将所传入的所有参数的子属性都复制到child对象里,以便产生一个新对象。

那如何我们只想混入部分属性呢?该个如何做?其实我们可以使用多余的参数来定义需要混入的属性,例如mix(child,parent,method1,method2)这样就可以只将parent里的method1和method2混入到child里。上代码:

//Car
varCar=function(settings){
this.model=settings.model||'nomodelprovided';
this.colour=settings.colour||'nocolourprovided';
};

//Mixin
varMixin=function(){};
Mixin.prototype={
driveForward:function(){
console.log('driveforward');
},
driveBackward:function(){
console.log('drivebackward');
}
};

//定义的2个参数分别是被混入的对象(reciving)和从哪里混入的对象(giving)
functionaugment(receivingObj,givingObj){
//如果提供了指定的方法名称的话,也就是参数多余3个
if(arguments[2]){
for(vari=2,len=arguments.length;i<len;i++){
receivingObj.prototype[arguments[i]]=givingObj.prototype[arguments[i]];
}
}
//如果不指定第3个参数,或者更多参数,就混入所有的方法
else{
for(varmethodNameingivingObj.prototype){
//检查receiving对象内部不包含要混入的名字,如何包含就不混入了
if(!receivingObj.prototype[methodName]){
receivingObj.prototype[methodName]=givingObj.prototype[methodName];
}
}
}
}

//给Car混入属性,但是值混入'driveForward'和'driveBackward'*/
augment(Car,Mixin,'driveForward','driveBackward');

//创建新对象Car
varvehicle=newCar({model:'FordEscort',colour:'blue'});

//测试是否成功得到混入的方法
vehicle.driveForward();
vehicle.driveBackward();


该方法使用起来就比较灵活了。

模式4:借用方法

一个对象借用另外一个对象的一个或两个方法,而这两个对象之间不会有什么直接联系。不用多解释,直接用代码解释吧:

varone={
name:'object',
say:function(greet){
returngreet+','+this.name;
}
};

//测试
console.log(one.say('hi'));//"hi,object"

vartwo={
name:'anotherobject'
};

console.log(one.say.apply(two,['hello']));//"hello,anotherobject"

//将say赋值给一个变量,this将指向到全局变量
varsay=one.say;
console.log(say('hoho'));//"hoho,undefined"

//传入一个回调函数callback
varyetanother={
name:'Yetanotherobject',
method:function(callback){
returncallback('Hola');
}
};
console.log(yetanother.method(one.say));//"Holla,undefined"

functionbind(o,m){
returnfunction(){
returnm.apply(o,[].slice.call(arguments));
};
}

vartwosay=bind(two,one.say);
console.log(twosay('yo'));//"yo,anotherobject"

//ECMAScript5给Function.prototype添加了一个bind()方法,以便很容易使用apply()和call()。

if(typeofFunction.prototype.bind==='undefined'){
Function.prototype.bind=function(thisArg){
varfn=this,
slice=Array.prototype.slice,
args=slice.call(arguments,1);
returnfunction(){
returnfn.apply(thisArg,args.concat(slice.call(arguments)));
};
};
}

vartwosay2=one.say.bind(two);
console.log(twosay2('Bonjour'));//"Bonjour,anotherobject"

vartwosay3=one.say.bind(two,'Enchanté');
console.log(twosay3());//"Enchanté,anotherobject"


引用,查看原文


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐