首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > JavaScript >

正题:深刻理解JavaScript基于原型的面向对象

2012-11-22 
主题:深刻理解JavaScript基于原型的面向对象主题一、原型一、基于原型的语言的特点1 只有对象,没有类对象继

主题:深刻理解JavaScript基于原型的面向对象

主题一、原型


一、基于原型的语言的特点
1 只有对象,没有类;对象继承对象,而不是类继承类。
2 ?“原型对象”是基于原型语言的核心概念。原型对象是新对象的模板,它将自身的属性共享给新对象。一个对象不但可以享有自己创建时和运行时定义的属性,而且可以享有原型对象的属性。
3 除了语言原生的顶级对象,每一个对象都有自己的原型对象,所有对象构成一个树状的层级系统。root节点的顶层对象是一个语言原生的对象,其他所有对象都直接或间接继承它的属性。
显然,基于原型的语言比基于类的语言简单得多,我们只需要知道"用对象去创建对象",就可以在原型的世界里大行其道了!
二、基于原型的语言中对象的创建
创建有两个步骤1. 使用"原型对象"作为"模板"生成新对象这个步骤是必要的,这是每个对象出生的唯一方式。以原型为模板创建对象,这也是"原型"(prototype)的原意。2. 初始化内部属性这一步骤不是必要的。通俗点说,就是,对"复制品"不满意,我们可以"再加工",使之获得不同于"模板"的"个性"。这两个步骤很自然,也很好理解,比使用类构造对象从概念上简单得多了。对于习惯了java基于类的面向对象的语言的程序员, 这种"新颖"的生成对象的方式一定会让他们感到好奇。
三、原型,为复用代码而生
使用原型,能复用代码,节省内存空间?
举个例子,存在旧对象oldObject,它有一个属性name,值是’Andy’, 和一个名为getName()的方法,如果以该对象为原型创建一个新对象,

    当对象访问属性的时候,如果在内部找不到,那么会在原型对象中查找到属性;如果原型对象中仍然找不到属性,原型对象会查找自身的原型对象,如此循环下去,直至找到属性或者到达顶级对象。对象查找属性的过程所经过的对象构成一条链条,称之为原型链。newObject,oldObject和topObject就构成一条原型链。
    下面列出newObject的3种的查找属性情况
    newObject查找name,1 内部找不到,到原型对象中查找2 oldObject中查找到了name,成功返回;
    newObject查找toString1 内部找不到,到原型对象中查找2 oldObject中查找不到toString,到原型对象中查找3 topObject中查找到了toString,成功返回;
    newObject查找valueOf1 内部找不到,到原型对象中查找2 oldObject中查找不到valueOf,到原型对象中查找3 topObject中还是找不到,而且topObject是顶层对象,所以返回错误或者空值。
    对象会通过原型链动态地查找属性,对象的所拥有的属性并不是静态的。如果原型链上的一个对象发生的改变,那么这个改变也会马上会反应到在原型链中处于该对象下方的所有对象。

    三、继承如果以oldObject为原型创建了newObject,那么可以说newObject继承了oldObject。在java中 通过语句class Cat extends Animal定义Cat类继承Animal类,Cat类产生的实例对象便拥有了Animal类中定义的属性。类似地,在基于原型的语言中, 通过cat = create(animal)创建以animal对象为模板的cat对象,cat对象便拥有了animal对象中的属性,因此可以说cat对象继承了anmial对象。?
    四、小结原型的本质就是对象引用原型对象的属性,实现代码复用。基于原型的语言是以原型对象为模板创建对象newObject = create(oldObject)。


    主题二、深刻理解JavaScript基于原型的面向对象
    一、饱受争议的javascript
    javascript本质上是基于原型的语言,但是却引入了基于类的语言的new关键字和constructor模式,导致javascript饱受争议。
    javascript的作者Brendan Eich 1994年研发这门语言的时候,C++语言是最流行的语言,java1.0即将发布,面向对象编程势不可挡,于是他认为,引入new关键字可以使习惯C++/java程序员更容易接受和使用javascript。
    实际上,事实证明引入new是个错误的决定。
    C++/java程序员看到new一个 function的时候,他们会认为js通过function创建对象,function相当于类,接着他们会尝试在js挖掘类似java/C++面向类的编程特性,结果他们发现function没有extends,反而有个很奇怪的prototype对象,于是他们开始咒骂,js的面向对象太糟糕了。确实,new的引入让他们以为js的面向对象与java/C++类似,实际上并不是,如果不是以原型本质去理解js的面向对象,注定要遭受挫折,new,prototype,__proto__都是javascript实现原型的具体手段。
    另一方面,理解原型的程序员,也表示不高兴,因为居然要使用new function的语法来间接实现原型继承,三行代码才做到最基本的原型继承,下面是实现对象newObject继承对象oldObject的代码,

                            ?4)?可以修改或替换构造函数都会默认关联的原型对象。需要注意的的是,不少资料说,如果是使用自定义的对象替换了构造函数f默认关联的原型对象,最好添加一行代码

                                    ?

                                    ??从这个图,我们可以直观地看到??1) 所有对象都有自己的原型对象。所有构造函数的原型对象都是Function.prototype,Object.prototype是最顶层的对象。我们可以在Function.prototype上增加方法,那么在原型链下方的函数,就可获得这些方法,同理我们可以在Object.prototype上增加方法,那么js所有对象都拥有了这个方法。??2) 通过原型继承,所有对象构成了一个完整的系统??3) 我相信你能够发现更多有趣的的东西.如果你觉得这篇文章不值得一看,那么请至少看看这张图片,结合这张图片重新思考下js原型的理念,应该能给你一些有益的回报。
                                    6. 构造器模式的best practice ???1) 方法最好放在原型对象中,让每个实例对象都共享同一个方法。如果方法放在构造函数中,那么每个对象都有自己独立的一份方法代码,浪费内存。??2) 字段变量(fields,variables)最好放在构造函数中,让每个实例对象都具有一份自己的字段。除非要在所有子类中共享,实现类似静态变量的效果,才把字段放在原型中。??3) 继承层次不宜过深,原型链查找会耗费时间。??例如,??上面第5)点中的代码片段中,??1)Employee和Coder的方法都放在了原型中??2)Coder产生的实例对象虽然继承自匿名employee对象(new Employee('')),拥有name属性,但是为了每个Coder产生的实例对象都拥有属于自己的一份name属性,我们选择在构造函数中重复定义name属性,覆盖匿名employee对象的name属性。??四、模拟基于类的面向对象
                                    1. 该不该模拟类javascript是基于原型的语言,具有强大的表达能力,足可以模拟基于类的面向对象。相信大家也看过不少模拟类的js代码,这里不打算罗列。
                                    但是,js毕竟是原型继承的语言,应该要按照原型继承的思维去表达面向对象,而不是用类的思维,这样才能表现出js的真正的威力。如果要模拟的话,模拟一些最基本的操作就可以,不要尝试深入模拟基于类的语言的复杂特性,否则会犯下跟Brendan Eich同样的错误。模拟的出发点是方便程序员能够更容易地使用js面向对象,但是理解了构造器模式和原型链的前提下,没有模拟的必要,只需要封装一些常用的操作就OK了。js的对象没有类型,根本不需要像java的对象那样需要关心自己的类继承体系以检查类型转换是否正确,所以模仿类继承没有意义。在js中只需要关心对象的内容,关心对象能否继承其他对象的属性就足够了。
                                    我就曾经是一个被误导的程序员。看别人写的面向对象教程,以为js需要我们开发一些函数,才能使用面向对象。我用过prototype.js 的Class.create,那时候我的感觉很不爽,我抱怨js为什么连最基本的class都没有。
                                    如果让我写一篇文章,介绍js的面向对象,我会先教会读者领会这个函数,
                                                <script>??????????/**??????*?以原型对象为模板创建出新对象??????*?这个函数已经被Chrome和IE9采用,所以需要有个判断这个函数是否已经存在,Crockford的影响力可见一斑??????*/??????if(!Object.create){??????????Object.create?=?function(oldObject){??????????????function?F(){};??????????????F.prototype?=?oldObject;??????????????return?new?F();??????????}??????}??????/**??????*?在构造函数的原型对象上添加方法??????*?非常推荐这个函数,因为这个函数能够培养出在原型对象中定义方法的良好习惯??????*/??????Function.prototype.method?=?function(name,func){??????????if(!this.prototype[name]){??????????????this.prototype[name]?=?func;??????????????return?this;??????????}??????};??????/**??????*?使构造函数“继承”其他构造函数??????*?实际上是将构造函数的原型对象替换为另外构造函数产生的对象??????*???????*/??????Function.method('inherits',function(F){??????????this.prototype?=?new?F();??????????return?this;??????});????????????/**??????*?创建父对象方法的副本??????*/??????Object.method('superior',function(methodName){??????????var?that??=??this;??????????var?method?=?this[methodName];??????????return?function(){??????????????return?method.apply(that,arguments);??????????};??????});??????/*****************************************??????*?使用函数创建对象??????*?1?使用函数的闭包实现私有属性??????*?2?子对象可以调用父对象的方法??????*****************************************/??????function?employee(name){??????????var?object?=?{};??????????//name属性是私有变量??????????var?name?=?name;??????????//定义一个getName私有变量的目的是,如果其他方法想调用getName方法,它们可以直接调用getName而不是object.getName。??????????//如果该object.getName被外部篡改了,那么其他引用var?getName的方法并不会收到影响,这样程序的健壮性有保证??????????var?getName?=?function(){??????????????return?name;??????????}??????????//getName对外公开??????????object.getName?=?getName;??????????return?object;??????}????????function?coder(name,language){??????????var?object?=?employee(name);??????????//获取父对象getName函数的副本??????????var?superGetName?=?object.superior('getName');??????????var?language?=?language;??????????var?getLanguage?=?function(){return?language;};??????????//调用父对象的方法??????????var?getName?=?function(){??????????????return??"my?name?is?"?+?superGetName(name);??????????};????????????object.getName?=?getName;????????????return?object;??????}??????var?e1?=?employee('Jack');??????alert(e1.name);//undefined??????alert(e1.getName());//Jack????????????var?c1?=?coder('Jackson','Java');??????alert(c1.getName());//My?name?is?Jack??????????</script>??
                                              ? 附录推荐一些极好的关于JS面向对象的文章(每一篇都严重推荐,尤其是crockford和他的《JavaScript: The Good Parts》)
                                              crockford大师http://javascript.crockford.com/javascript.htmlhttp://javascript.crockford.com/prototypal.htmlhttp://javascript.crockford.com/inheritance.htmlhttp://www.crockford.com/javascript/private.html
                                              MDNhttps://developer.mozilla.org/en-US/docs/JavaScript/Guide/Details_of_the_Object_Model
                                              微软杂志http://msdn.microsoft.com/zh-cn/magazine/cc163419.aspx#S4
                                              MSDNhttp://msdn.microsoft.com/en-us/library/dd282900(v=vs.85).aspxhttp://msdn.microsoft.com/en-us/library/dd229916(v=vs.85)
                                              阮一峰http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.htmlhttp://www.ruanyifeng.com/blog/2012/07/three_ways_to_define_a_javascript_class.html?20120830102326#comment-last

                                              http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html

                                              http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html

                                              ?

                                              others

                                              http://bonsaiden.github.com/JavaScript-Garden/zh/

热点排行