JavaScript继承的特性与实践应用深入详解
本文详细讲述了JavaScript继承的特性与实践应用。分享给大家供大家参考,具体如下:
继承是代码重用的模式。JavaScript可以模拟基于类的模式,还支持其它更具表现力的模式。但保持简单通常是最好的策略。
JavaScript是基于原型的语言,也就是说它可以直接继承其他对象。
1伪类
JavaScript的原型不是直接让对象从其他对象继承,而是插入一个多余的间接层:通过构造函数来产生对象。
当一个函数被创建时,Function构造器产生的函数对象会运行这样类似的代码:
this.prototype={constructor:this};
新的函数对象新增了一个prototype属性,它是一个包含了constructor属性且属性值为该新函数的对象。
当采用构造器调用模式,即用new去调用一个函数时,它会这样执行:
Function.method('new',function(){ varthat=Object.create(this.prototype);//创建一个继承了构造器函数的原型对象的新对象 varother=this.apply(that,arguments);//调用构造器函数,绑定this到新对象 return(typeofother==='object'&&other)||that;//如果构造器函数的返回值不是对象,就直接返回这个新对象 });
我们可以定义一个构造器,然后扩充它的原型:
//定义构造器并扩充原型 varMammal=function(name){ this.name=name; }; Mammal.prototype.get_name=function(){ returnthis.name; }; Mammal.prototype.says=function(){ returnthis.saying||''; };
然后构造实例:
varmyMammal=newMammal('Herbthemammal'); console.log(myMammal.get_name());//Herbthemammal
构造另一个伪类来继承Mammal(定义构造器函数并替换它的prototype):
varCat=function(name){ this.name=name; this.saying='meow'; }; Cat.prototype=newMammal();
扩充原型:
Cat.prototype.purr=function(n){ vari,s=''; for(i=0;i我们使用method方法定义了inherits方法,来隐藏上面这些丑陋的细节:
/** *为Function.prototype新增method方法 *@paramname方法名称 *@paramfunc函数 *@returns{Function} */ Function.prototype.method=function(name,func){ if(!this.prototype[name])//没有该方法时,才添加 this.prototype[name]=func; returnthis; }; Function.method('inherits',function(Parent){ this.prototype=newParent(); returnthis; });这两个方法都返回this,这样我们就可以以级联的方式编程啦O(∩_∩)O~
varCat=function(name){ this.name=name; this.saying='meow'; }.inherits(Mammal).method('purr',function(n){ vari,s=''; for(i=0;i虽然我们有了行为很像“类”的构造器函数,但没有私有环境,所有的属性都是公开的,而且不能访问父类的方法。
如果在调用构造函数时忘记加上new前缀,那么this就不会被绑定到新对象上,而是被绑定到了全局变量!!!这样我们不但没有扩充新对象,还破坏了全局变量环境。
这是一个严重的语言设计错误!为了降低出现这个问题的概率,所有的构造器函数都约定以首字母大写的形式来命名。这样当我们看到首字母大写的形式的函数,就知道它是构造器函数啦O(∩_∩)O~
当然,更好的策略是根本不使用构造器函数。
2对象说明符
有时候,构造器需要接受一大堆参数,这很麻烦。所以在编写构造器时,让它接受一个简单的对象说明符会更好:
varmyObject=maker({ first:f, middle:m, last:l });现在这些参数可以按照任意的顺序排列咯,而且构造器还能够聪明地为那些没有传入的参数使用默认值,代码也变得更易阅读啦O(∩_∩)O~
3原型
基于原型的继承指的是,一个新对象可以继承一个旧对象的属性。首先构造出一个有用的对象,然后就可以构造出更多与那个对象类似的对象。
/** *原型 */ varmyMammal={ name:'Herbthemammal', get_name:function(){ returnthis.name; }, says:function(){ returnthis.saying||''; } }; //创建新实例 varmyCat=Object.create(myMammal); myCat.name='Henrietta'; myCat.saying='meow'; myCat.purr=function(n){ vari,s=''; for(i=0;i这里用到了create方法来创建新的实例:
Object.create=function(o){ varF=function(){ }; F.prototype=o; returnnewF(); }4函数化
目前为止看到的继承模式的问题是:无法保护隐私,对象的所有属性都是可见的。有一些无知的程序员会使用伪装私有的模式,即给一个需要私有的属性起一个古怪的名字,并希望其他使用代码的程序员假装看不到它们!
其实有更好的方法:应用模块模式。
我们先构造一个生成对象的函数,它有这些步骤:
①.创建新对象。这有四种方式:
【1】构造一个对象字面量。
【2】调用一个构造器函数。
【3】构造一个已存在对象的新实例。
【4】调用任意一个会返回对象的函数。
②.定义私有实例变量与方法。
③.为这个新对象扩充方法,这些方法拥有特权去访问这些参数。
④.返回这个新对象。函数化构造器的伪代码如下:
varconstructor=function(spec,my){ varthat,其他私有变量; my=my||{}; //把共享的变量和函数添加到my中 that=一个新对象 //添加给that的特权方法 returnthat; };spec对象包含了需要构造一个新实例的所有信息,它可以被用到到私有变量或者其他函数中。
my对象为在一个继承链中的构造器提供了共享的容器,如果没有传入,那么会创建一个my对象。创建特权方法的方式是:把函数定义为私有方法,然后再把它们分配给that:
varmethodical=function(){ ... }; that.methodical=methodical;这样分两步定义的好处是:私有的methodical不受这个实例被改变的影响。
现在,我们把这个模式应用到mammal示例中:
varmammal=function(spec){ varthat={}; that.get_name=function(){ returnspec.name; }; that.says=function(){ returnspec.saying||''; }; returnthat; }; varmyMammal=mammal({name:'Herb'}); console.log(myMammal.get_name());//Herb varcat=function(spec){ spec.saying=spec.saying||'meow'; varthat=mammal(spec); that.purr=function(n){ vari,s=''; for(i=0;i函数化模式还能调用父类的方法。这里我们构造一个superior方法,它会返回调用某个方法名的函数:
//返回调用某个方法名的函数 Object.method('superior',function(name){ varthat=this, method=that[name]; returnfunction(){ returnmethod.apply(that,arguments); }; });现在创建一个coolcat,它拥有一个可以调用父类方法的get_name:
varcoolcat=function(spec){ varthat=cat(spec), super_get_name=that.superior('get_name'); that.get_name=function(n){ return'like'+super_get_name()+'baby'; }; returnthat; }; varmyCoolCat=coolcat({name:'Bix'}); console.log(myCoolCat.get_name());//likemeowBixmeowbaby函数化模式有很大的灵活性,而且可以更好地实现封装、信息隐藏以及访问父类方法的能力。
如果对象所有的状态都是私有的,那么就称为防伪对象。这个对象的属性可以被替换或删除,但这个对象的状态不受影响。如果用函数化模式来创建对象,并且这个对象的所有方法都不使用this或that,那么这个对象就是持久性的,它不会被入侵。除非存在特权方法,否则不能访问这个持久性对象的内部状态。
5事件处理函数
可以构造一个能够给任何对象添加简单事件处理特性的函数。这里,我们给这个对象添加一个on方法,fire方法和私有的事件注册对象:
vareventuality=function(that){ varregistry={}; /** *触发事件 * *使用'on'方法注册的事件处理程序将被调用 *@param可以是包含事件名称的字符串,或者是一个拥有type属性(值为事件名称)的对象。 */ that.fire=function(event){ vararray, func, handler, i, type=typeofevent==='string'?event:event.type; //如果这个事件已被注册,则遍历并依序执行 if(registry.hasOwnProperty(type)){ array=registry[type]; for(i=0;i可以在任何单独对象上调用eventuality,授予它事件处理方法。也可以在that被返回前,在构造函数中调用它:
eventuality(that);JavaScript弱类型的特性在此是一个巨大的优势,因为我们无须处理对象继承关系中的类型O(∩_∩)O~
感兴趣的朋友还可以使用本站在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行结果。
更多关于JavaScript相关内容还可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。