浅谈javascript面向对象编程.doc_第1页
浅谈javascript面向对象编程.doc_第2页
浅谈javascript面向对象编程.doc_第3页
浅谈javascript面向对象编程.doc_第4页
浅谈javascript面向对象编程.doc_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

浅谈javascript面向对象编程默认分类 2009-07-19 20:23 阅读81 评论1 字号: 大大 中中 小小 这周心血来潮,翻看了现在比较流行的几个JS脚本框架的底层代码,虽然是走马观花,但也受益良多,感叹先人们的伟大感叹是为了缓解严肃的气氛并引出今天要讲的话题,”javascript面向对象编程”,接下来,我们围绕面向对象的几大关键字:封装,继承,多态,展开。封装:javascript中创建对象的模式中,个人认为通过闭包才算的上是真正意义上的封装,所以首先我们先来简单介绍一下闭包,看下面这个例子: function myInfo() var name =老鱼,age =27; var myInfo = my name is + name + i am + age +years old; function showInfo() alert(myInfo); return showInfo; var oldFish = myInfo(); oldFish(); 是不是很眼熟呢?没错了,这其实就是一个简单的闭包应用了。简单解释一下:上面的函数myInfo中定义的变量,在它的内嵌函数showInfo中是可访问的(这个很好理解),但是当我们把这个内嵌函数的返回引用赋值给一个变量oldFish,这个时候函数showInfo是在myInfo函数体外被调用,但是同样可以访问到定义在函数体内的变量。oh yeah!总结一下闭包的原理吧:函数是运行在定义他们的作用域中而不是调用他们的作用域中。其实返回一个内嵌函数也是创建闭包最常用的一种方法!如果觉得上面的解释太抽象的话,那么我们一起重塑上面的函数,看看这样是否层次鲜明一些: var ioldFish = function(name,age) var name = name,age = age; var myInfo = my name is + name + i am + age +years old; return showInfo:function() alert(myInfo); ioldFish(老鱼,27).showInfo(); 上例中的编码风格是ext yui中比较常见的,公私分明,一目了然。通过闭包,我们可以很方便的把一些不希望被外部直接访问到的东西隐藏起来,你要访问函数内定义的变量,只能通过特定的方法才可以访问的到,直接从外部访问是访问不到的,写的挺累,饶了一圈终于转回来了,封装嘛,不就是把不希望被别人看到的东西隐藏起来嘛!哈哈上例如果转换成JQ的风格的话,应该如下例所写, 这样的封装模式属于门户大开型模式,里面定义的变量是可以被外部访问到的(下面的例子如果你先实例化一个对象,然后在函数外部访问对象的name或者age属性都是可以读取到的)当然这种模式下我们可以设置一些”潜规则”,让团队开发成员明白哪些变量是私用的,通常我们人为的在私有变量和方法前加下划线”_”,标识警戒讯号!从而实现”封装”! var ioldFish = function(name,age) return ioldFish.func.init(name,age); ; ioldFish.func = ioldFtotype = init:function(name,age) = name; this.age = age; return this; , showInfo:function() var info = my name is + +i am +this.age+years old; alert(info); ; ioldFtotype = ioldFish.func; ioldFish( 老 鱼,27).showInfo(); /var oldFish = new ioldFish(老鱼,27); /alert(oldF); 可能有人会问,哪种模式好呢?这个怎么说呢?两种方式都有优缺点,结合着用呗!总之一个原则,一定一定不能直接被外部对象访问的东西,就用闭包封装吧。”一定一定”四个字很深奥,不断实践中才能体会真谛!继承:提到这个的时候,要顺便再补充一句:闭包封装中的一个缺点,不利于子类的派生,所以闭包有风险,封装需谨慎!直观起见,下面例子中创建对象的方式,采用”门户大开型”模式。在javascript中继承一般分为三种方式:”类式继承”,”原型继承”,”掺元类”。下面简单的介绍一下三类继承方式的原理。A.类式继承:这个是现在主流框架中常用的继承方式,看下例: var Name = function(name) = name; ; Ntotype.getName = function() alert(); ; var Fish = function(name,age) Name.call(this,name); this.age = age; ; Ftotype = new Name(); Ftotype.constructor = Fish; Ftotype.showInfo = function() alert(this.age); var ioldFish = new Fish(老鱼,27); ioldFish.getName(); 上述子类Fish中并没定义getName方法,但是子类Fish的实例对象ioldFish依然调用到了该方法,这是因为子类Fish继承了超类Name中定义的getName方法。解释一下,这里子类Fish的prototype指到了超类的一个实例,在子类Fish中虽然没有申明getName方法,但是根据原型链原理,会向prototype指向的上一级对象中去查找是否有该方法,如果没找到该方法,会一直搜索到最初的原型对象。这其实也就是继承的原理了。这里特别说明一下,Ftotype.constructor = Fish;这句,由于默认子类的prototype应该是指向本身的,但是之前把prototype指向到了超类的实例对象,所以在这里要把它设置回来。当然这里可以把相关代码通过一个函数来组织起来,起到伪装extend的作用,这里不再阐述,可以关注本人下篇博文B.原型继承,从内存性能上看优于类式继承。 function clone(object) var F = function(); F.prototype = object; return new F(); ; var Name = name:whos name, showInfo:function() alert(); ; var Fish = clone(Name); /F = 老鱼; Fish.showInfo(); 很明显,原型继承核心就是这个clone函数,同样是原型链的原理,不同的是它直接克隆超类,这样的话子类就继承了超类的所有属性和方法.特别说一下,这类继承并不需要创建构造函数,只需要创建一个对象字变量,定义相应的属性和方法,然后在子类中只需要通过圆点”.”符号来引用属性和方法就可以了.C.掺元类:把一些常用通用性比较大的方法统一封装在一个函数中,然后通过下面这个函数分派给要用到这些方法的类还可以针对不同的类,选择性的传递需要的方法。 function agument(receveClass,giveClass) if(arguments2) var len = arguments.length; for(i=2;ilen;i+) receveCtotypeargumentsi = giveCtotypeargumentsi; else for(method in giveCtotype) if(!receveCtotypemethod) receveCtotypemethod = giveCtotypemethod; ; var Name = function(); Ntotype = sayLike:function() alert(i like oldfish); , sayLove:function() alert(i love oldfish); var Fish = function(); var ioldFish = new Fish(); agument(Fish,Name,sayLove); ioldFish.sayLove(); ioldFish.sayLike(); 多态:个人觉得这个比较抽象,很难言传,所以下面就从重载和覆盖两个方面来简单阐述一下。重载:上面这个例子中agument函数初始带了两个参数,但是在后面的调用中,agument(Fish,Name,”sayLove”)同样可以带入任意多个参数,javascript的重载,是在函数中由用户自己通过操作arguments这个属性来实现的。覆盖:这个很简单,就是子类中定义的方法如果与从超类中继承过来的的方法同名,就覆盖这个方法(这里并不是覆盖超类中的方法,注意一下),这里就不累赘了!最后重点着墨说一下this和执行上下文,在前面举的封装例子中,this都是表示this所在的类的实例化对象本身,但是并不是千篇一律的,打个比方,通过HTML属性定义的事件处理代码,见如下代码: var Name = function(name) = name; this.getName = function () alert(); ; var ioldFish = new Name(老鱼), btn = document.getElementById(btn); btn.onclick = ioldFish.getName; /btn.onclick = function()ioldFish.getName.call(ioldFish); 上例中点了按钮以后弹出框里并没有显示出实例对象的属性,这是因为this的执行上下文已经改变了,他现在所在的上下文应该是input这个HTML标签,但是该标签又不存在getName这个属性,所以自然无法输出这个属性的属性值了!从这个例子我们不难看出:执行上下文是在执行时才确定的,它随时可以变。当然你可以去掉上面我注释掉的那段代码,通过call改变this的执行上下文,从而获取getName方法。apply方法同样可以实现改变执行上下文的功能,不过在prototype框架中发现了一个更为优美的实现方法bind。看一下这个方法的实现吧,不得不感叹先人的伟大Ftotype.bind = function(obj) var method = this, temp = function() return method.apply(obj, arguments); ; 相信如果能看明白的话,您已经可以靠这些知识点,去写一个简单的脚本框架了,多多实践,相信不久的将来就能高手进级了!如果没看明白,也不用着急,面向对象本来就有些抽象,多练习练习,应该OK的了,加油javascript面向对象之我解今天要用js实现一些客户端功能,考虑到业务逻辑,使用OO的开发方式会很方便,于是认真查看了相关的几篇文章,有一些心得体会。 首先是定义类。js中定义类是使用function,实例化使用new操作符: 复制 保存function class1() this.a = class1; this.m1 = function() alert(class1.m1); totype.m2 = fucntion() alert(class1.m2);var c1 = new class1();在js中,Function和Object是两个最基础的类,js中的任何对象、实例、函数都同时是Function和Object的实例,这可以用instanceof来验证,这一点有特殊的作用,后面的一些魔法全靠这一点支撑。 function有两种用法,一种如上,是常用的形式;另一种是运行时动态创建,如 复制 保存var add = new Function(x, y, return (x+y);创建的结果是一个Function对象。这可以解释我之前看到的“奇怪”的用法。如上,this.m1=function().的用法中,实际上后面的function句自动产生了一个匿名的fucntion对象,然后赋值给m1属性,随即class1就具有了m1方法。之后又用totype.m2=fucntion().句定义了m2方法,实际上也是得到了匿名的function对象,然后赋值给m2属性。当然,这两种定义方法的形式是由区别的,后面会讲到。既然得到的是一个对象,可以使用变量指代,当然就可以作为函数调用的参数了,也就可以轻松而自然的实现函数回调了。这比C#之类的语言实现回调更方便自然。 这里再多啰嗦一句,js类在定义的时候可以定义属性,如示例中的a属性。除此之外,实例的属性是可以动态添加的。如果使用c1.b=b,则c1自动具有了属性b。这种动态添加的属性是不会扩散到其他实例的,即如果有另一个实例c2,则c2仍然只有属性a而无属性b。 如上示例,任何类(class1)的函数有两种实现方式,一种的内联的方式,即在定义function的时候,将方法写在body中;另一种是使用prototype,可以在任何地方定义。对于类的实例(class1的实例c1),同时拥有这两种方式定义的方法。因为内联定义的优先级高,在实例调用方法时,首先查找内联定义,然后转到prototype的定义中查找。因此,prototype定义的方法会被内联定义覆盖掉。如果真的发生了方法覆盖,要想使用prototype定义的方法,至少有两种方法(以下假设class1中使用同时使用了上述两种形式定义了方法m1)。 第一种是使用delete,首先delete掉m1方法,根据优先级,自然delete掉的是内联的m1方法。什么,不明白为什么还能delete掉方法?首先。js是动态语言;其次,记住方法也只是一个function实例,在class1中只是一个特殊属性而已。 复制 保存var c1 = new class1();delete c1.m1; /去掉内联的m1方法c1.m1(); /调用prototype的m1方法第二种是使用apply方法。至于apply的使用方法,可以查看js手册。 复制 保存var c1 = new class1();totype.m.apply(c1); /使用prototype中定义的m方法代替c1的m方法用new实例化的对象仍然同时是Function对象和Object对象,包括这两者的所有实例属性和方法。这里不是很好理解,想想看,我们定义的类同时是Function对象和Object对象的实例,然后实例化后,实例也同时是Function对象和Object对象的实例,有没有一点像父亲和儿子的爸爸是同一个人?呵呵,有点恶心了,还希望高手出来详细剖析一下Function对象和Object对象以及js里面的类型关系。 这里把js和ruby做个比较。js和ruby都有一个默认的全局对象,js中是Global对象,所用定义的全局变量和函数都是Global的成员。从这一点看,js也有一点oo的感觉(至少是形式上):-)。ruby中,同时存在class变量和实例变量,js也是。这种设计是脚本语言的优势,可以在运行时随时改变class的定义,非常灵活和强大。同时两种脚本中的所有类型都不是封闭的,而是开放的,用户可以给语言内置的所有类型添加额外的属性和方法。 另外闲扯一下有趣的话题:js的反射。js中任何对象(function和object)都可以使用如下方式遍历所有成员: 复制 保存var info = typeof(obj)+n;for(p in obj) info += p + n; alert(info);同样使用反射,可以这样调用方法和属性: 复制 保存alert(c1a);c1m1();可以这样考虑,在js的类中,本质上不区分方法和属性,因为方法也就是一个特殊的function对象而已。所以,js类中只存在一种东西:属性。可以猜测js类就是一个hash实现,将所有的属性按照名称和值存入。即可以按照一个类对待,也可以简单的作为一个hash对待。当我使用c1m1()形式的调用时,实际过程就是取出值,也就是一个function对象,然后执行之。 然后是继承,比较多的人推荐使用prototype实现继承,因为其强大和灵活。这里不详细介绍prototype,仅探讨我的一些思考,需要了解的读者可以参考文后的相关文章。在读者继续阅读之前,需要有以下几个认识: 1、prototype是对象,而不是简单值(数值、字符串等),因此是引用类型。简单的将prototype赋值给另一个变量,实际上只是浅拷贝了一个引用,拥有的是一个对象。既然对prototype的简单赋值是前拷贝,保存的是引用,自然对任何一个引用的修改都会影响所有的引用。 2、对于上文介绍的两种方法定义方式所定义的方法在类的实例中有着不同的处理方式。经过我设计的几个实验和阅读相关文章,认为一个类的实例实际上也是一个prototype,这个prototype包含两部分。一部分是内联定义的方法,prototype中直接保存了这种方法的深拷贝。另一部分是类所定义的prototype,在实例这个prototype中是以一个引用保存。这点很重要,是后面理解prototype继承中的种种陷阱的关键之一; 3、一个类(class1)的prototype就是这个类的一个不完全实例(之前没有相通这点,仔细看看prototype模式,自然就可以明白)。不完全实例是指prototype不包括内联定义的方法和属性。这样,自然可以理解“类的实例实际上也是一个prototype”了。 4、到现在为止,可以看到js中除掉简单类型,所有的类型和实例都是object类型,即便是神秘的prototype也是(其实理解了prototype也只是一个object而已,也就不再神秘了)。所以prototype可以动态增加方法和属性。 笔者是边写边想,边想边写,因此又想到一个有趣的问题,其实在js的实现中就有一个很棒的继承实现,即一个类(class1)是对其prototype(totype)的继承,子类化的部分就是类定义中的内联部分。所以类的实例有了所有的内联和prototype中的所有方法和属性,而且也有合理的覆写行为-子类可以覆写父类的方法和属性。缺点主要在于类的所有实例都引用了类的prototype,因此对类的prototype的修改会影响到所有实例。其实再想想,这也算不上是缺点,因为我们的思维固化在静态语言的模式中。对于动态语言,语言本身提供了大量不可思议的功能,很多在静态语言中不可能的事情可以实现了,然而陷阱也更多了,很多问题需要语言的使用者依靠编程习惯和技能来避免,对语言使用者的要求更高一些。对于这个所谓的缺点,一个有益的场合是我们确实需要修改所有实例的行为,这时它反过来是一个大大的优点(接下来的实例中会经常看到这种应用)。回到这个问题来,一般而言我们其实不会修改类的prototype的。现实中最有可能产生问题的应该是prototype中定义的object类型的复杂属性,这个在javascript的prototype继承有详细讲解。 另一个联想到的问题是微软的Asp.NetAjax实现中,几乎类的所有方法都是内联定义的。有批评说这样的实现会降低效率,这个我自然同意。但是同时是不是有另外一个优点:如果同样使用了prototype继承法,则这种继承刚好类似于一个类(class1)是对其prototype(totype)的继承,实现了良好的覆写行为,同时因为基类的所有方法仍然存在于prototype中,对于某些需要调用基类方法的情形也可以满足。不知道微软当时是不是考虑过这个问题,呵呵。 下面列举两种实现方式: 第一种摘自javascript的prototype继承: 复制 保存/类的继承-海浪版Ftotype.Extends = function (parentClass) var Bs = new Function(); Btotype = parentCtotype; totype = new Bs(); totype.Super = parentClass; totype.constructor = this;文中没有举例如何使用,我想大概是这样的方式使用: 复制 保存function class1() function class2() Super();class2.Extends(class1);var c1 = new class1();var c2 = new class2();这种实现本希望解决原文中提出的四个缺点,但是我认为只是部分实现。 优点: 1、子类的构造函数得以保留,父类的构造函数可以在子类构造时调用了; 2、父类中内联定义的方法和属性在子类中也以内联的方式出现; 3、得益于2的实现,如果在父类中内联定义了object复杂属性,由于子类中依然是内联属性,不会出现prototype中的复杂属性副作用。 缺点: 1、构造函数的实现有侵入性, 2、如果需要通过prototype定义方法和属性,则prototype的定义必须在Extends方法调用之后,否则所有的定义将会被Extends方法忽略掉。当然,对原Extends方法稍加改进,就可以消除这个缺点; 2、最主要的一点,虽然通过几个复杂的操作,子类仍然直接保留了父类的prototype。因此,如果基类的另一个子类(假设有的话,但既然使用了继承,一般会有另一个子类)如果使用prototype修改了基类的方法(既然是继承,子类覆写方法的需求是自然而常见的),会同时影响到所有的子类,这实际上意味着子类不能覆写父类的方法。当然,你可以不使用prototype的方式覆写,而使用内联的方式覆写,这时候prototype就不是什么大问题了。 第二种实现摘自JavaScript类的继承 复制 保存/为Object类添加静态方法:extendObject.extend = function(destination, source) for(property in source) destinationproperty = sourceproperty; return destination; /通过Object类为每个对象添加方法extendOtotype.extend = function(object) return Object.extend.apply(this, this, object); /定义class1function class1() /构造函数/定义类class1的成员totype= method:function() alert(class1); , method2:function() alert(method2); /定义class2function class2() /构造函数/让class2继承于class1并定义新成员totype=(new class1().extend( method:function() alert(class2); );/创建两个实例var obj1=new class1();var obj2=new class2();/试验obj1和obj2的方法obj1.method();obj2.method();obj1.method2();obj2.method2();这里和大家讨论一下其中几个“高级”用法。一是prototype=.的用法。前面说过prototype也只不过是一个对象而已,当然任何一个对象都可以赋值给它。后面的.可以理解成定义了一个匿名的类,然后直接实例化成一个对象,再赋值给prototype。二是Ototype.extend=function(object).的用法。之前谈到prototype的陷阱时讲过,prototype的这一特性可以动态添加方法,这里就应用了这一特性。这里我有一点不明白,为什么使用returnObject.extend.apply(this,this,object)而不是returnObject.extend(this,object)?感觉这两种方式没什么区别,因为Object.extend方法完全没有使用实例内部的任何属性,没有使用this引用。 我对这种实现的看法: 优点: 1、prototype的定义和extend方法可以任意使用,没有调用的次序约束。 2、由于extend方法实现得很巧妙,这种继承方法可以实现多继承。我不喜欢多继承,宁愿称其为mixin,因为这和ruby中的mixin很相似。 缺点: 1、不支持父类的构造函数; 2、不合适的方法覆写行为。按照这种设计,父类甚至可以覆写子类的方法。 我想,一个比较完善的js继承应该实现以下特性: 1、子类能够继承所有父类的方法和属性,包括内联定义的和prototype定义的; 2、有合理的覆写行为。子类可以覆写父类的方法和属性,而不是反过来。如果发生覆写,子类仍然应该保存对父类中被覆写的方法和属性的引用,因为子类的实现可能需要调用父类的实现; 3、子类的构造函数中必须调用父类的构造函数,因为父类可能必须参数初始化; 这里给出我的初步实现,还没有深入测试,供参考。 复制 保存Ftotype.inherit = function(baseClass) totype.base = new baseClass(); for

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论