首页 >web前端 >js教程 >JavaScript中的面向对象的程序设计简介

JavaScript中的面向对象的程序设计简介

一个新手
一个新手原创
2017-10-20 09:49:161081浏览

 本文内容目录顺序:

1、Object概念讲述; 2、面向对象程序设计特点; 3、JavaScript中类和实例对象的创建; 4、原型概念; 5、原型API; 6、原型对象的具体使用;7、深入理解使用原型对象实现继承;8、多态:override—重写


1、Object概念讲述:

面向对象程序设计我们首先要明白什么是对象,我在在引用数据类型中也说明了关于对象的这一概念;所以开篇我首先讲述一下对象的概念【Object——对象】:

什么是对象:对象就是存储一个事物的属性和功能的一块存储空间,在起一个名字;

何时使用对象:只要集中保存一个事物的属性和功能时;

如何创建一个对象:(3种方式)


第一种就是使用对象直接量创建一个对象并初始化对象成员:

var obj={ 
         name:tom, 
         age:25, 
         gender:M,
         intro:function (){
           alert("my name is tom") 
         } 
    }

第二种就是使用new关键字创建一个空对象再向对象里添加成员:


var boj=new Object();
obj.name="tom";
obj.age=25;
obj.gender="M";
obj.intro=function(){
    alert("my name is tom");
}

第三种就是使用构造函数

在这里稍微解释一下构造函数:

什么是构造函数:描述一类对象相同结构的函数;

为什么:使用对象直接量创建对象会产生大量相同的代码,代码重复不便于维护;

何时:只要反复创建多个相同结构的对象时;

作用:描述结构;创建对象,并存储属性值(将空对象构建成要求的结构);


//定义构造函数
function Person(name,age,gender){ //构造函数名我把它称作类型名首字母大写
    this.name=name;
    this.age=age;
    this.gender=gender;
    this.intro=function(){
      alert("my name is"+this.name);
    }
}

//调用构造函数
var obj=new Person('tom',25,'M',)//实例化了一个对象

PS:JavaScript中的对象本质就是一个关联数组,对象的成员的访问使用 . 和 [ ] ,如果访问的属性名是固定的就使用 . 访问,如果是动态生成的就用[ ]访问;所以在赋值操作中,如果属性名不存在那么就添加一个新属性不会报错;

obj['属性名']  /  obj['方法名']() ;

obj.属性名     / obj.方法名() ;                  


2、面向对象程序设计特点:

三大特点:封装、继承和多态;

封装:将现实中一个事物的属性和功能集中定义在一个对象中称之为封装;(现实中事物的属性会成为程序中对象的属性,现实中事物的功能会成为程序中对象的方法。属性:对象中描述对象的一个属性值的变量;方法:对象中定义对象的一个功能的函数;)

继承:父对象的成员,子对象无需创建即可直接使用;(为什么:代码重用,节约内存;何时:只要同一类型的多个子对象有相同成员时;如何:JS中的继承都是通过原型对象来实现的;)

多态:同一个事物在不同的情况下表现出不同的状态——JS中的多态只支持重写(override);什么是重写:如果子对象觉得父对象的成员不好用时,可在子对象定义和父对象成员同名的自有成员;重写的结果就是:再调用该成员时,优先使用自有成员代替父对象中的共有成员,也体现了父子对象之间的差异;

3、JavaScript面向对象程序设计——类和实例对象的创建

JavaScript不是一门面向对象的编程语言,它是一门基于对象的编程语言;面向对象有一个非常明显的标志就是【类】的概念;比如说Java语言面向对象,它本身就有类的概念,通过这个类能够创建出任意多个具有相同属性和方法的具体对象,而在JavaScript中是没有类这个概念的,我能只能用代码去模拟,去构造这样一个相同的结构然后去使用这个模拟的类;

在开篇我们讲述了关于对象的实例,我们说面向对象就是可以通过一个类实例化出任意多个具有相同属性和方法的对象,那么JavaScript既然是基于对象的语言没有类的概念,我们是怎么通过代码模仿一个这样相同结构的类呢?有俩种方式,第一种就是工厂模式,我们知道工厂是可以生产出任意多款独立的产品,比如说生产某一款玩具他可以生产出很多个不同种类的玩具还有衣服工厂可以生产出任意不同款式的衣服,那么我们构造类的工厂模式也是类似的思维逻辑;第二种就是构造函数模式(默认标准模式)

在第二个序号下我们讲述了面向对象程序设计的三大特点:封装、继承和多态;简单来讲声明一个对象并定义对象成员就是封装,所以接下来的模拟类的创建和实例对象的创建都体现了封装这一概念;下面体会一下封装,封装的定义查看目录序号2;

第一工厂模式:

创建一个工厂(函数),首先创建一个普通的函数,函数名就是类型名,在函数里面创建一个模板对象,并且添加相应的对象成员(对象的属性和方法称之为对象的成员),最后返回模板对象;即通过一个函数,在函数内部创建了一个模板对象,最后函数返回模板对象,外部通过传入不同的参数使用不同的变量去调用函数就会得到不同的结果;PS:调用函数的不同变量就是实例化的不同对象;


function cellPhone(material,ms,mpx,cpu,ramc){
      var obj=new Object();
      obj.material=material ; //主屏材料
      obj.ms=ms ; //主屏尺寸
      obj.mpx=mpx ; //主屏分辨率
      obj.cpu=cpu ; //CPU型号
      obj.ramcl=ramc ; //RAM容量
   
     return  obj;
 }
 
var c1=cellPhone('IPS',4.3,'1280*720','高通骁龙','2GB');
var c2=cellPhone('IPS',5,'1200*620','高通骁龙','4GB');

第二构造函数:

通过构造函数的形式去调用,约定成俗函数名第一个字母大写就是一个类型名,形参列表定义参数必须由外部传入才能运行时的属性值,使用this关键字绑定属性到调用函数者的对象上(this是函数作用域中自动定义的引用函数调用时 · 前的对象的AO中的一个关键词默认创建);模板构建好以后再构建一个对象去使用模板内容——构建一个对象通常有几个步骤,具体几个呢?使用new关键字,参数传递,使用模板内容和返回对象!


function Student(name,age,gender,class){
    this.name=name;
    this.age=age;
    this.gender=gender;
    this.class=class;
    this.intro=function(){
              alert("我的名字叫"+name+"我来自"+class+"班");
           }
}

var  s1=new Student('小红',12,'FM','4');//创建了一个对象  小红
var  s2=new Student('小明',13,'M','4'); //创建了一个对象  小明

在上述代码中new做了4件事:1、创建一个新的对象,并将this自动指向新对象 ; 2、让新对象继承构造函数的原型对象;(下一部分讲解原型对象)3、执行构造函数中的语句,并向新对象中添加新成员并赋值;4、返回新对象的地址,保存到变量中;

 下面通过这一段代码来验证JavaScript中使用代码来模仿的类:


1function Student(name,age,gender,class){
    this.name=name;
    this.age=age;
    this.gender=gender;
    this.class=class;
    this.intro=function(){
              alert("我的名字叫"+name+"我来自"+class+"班");
           }
}
11var  s1=new Student('小红',12,'FM','4');//创建了一个对象  小红
12var  s2=new Student('小明',13,'M','4'); //创建了一个对象  小明


alert(s1==s2);   //false      验证了根据不同的模板创建出来不同的实例对象;
//alert(s1.constructor == Student); 返回true ; 表示s1的构造器是Student吗?结果时true;
//alert(s2.constructor == Student); 返回true ; 表示s2的构造器是Student吗?结果时true;
        
alert(p1 instanceof Student);//true  表示p1是Student的实例,返回false就说明不是;    
alert(p1 instanceof Object);//true   表示p1是Student的实例,返回false就说明不是;

通过以上一段代码使用三种方法验证了面对对象的类的概念,每一个实例对象都是基于一个相同的模板(构造函数)独立创建出来的对象;

构造函数三种创建实例对象的方法:

1、使用new关键字:var 变量名=new  类型名/构造函数名(属性值列表);

2、把构造函数当做普通函数调用:类型名/构造函数名(属性值列表);   //当做一个普通函数调用,此时构造函数是一个全局的函数,在全局环境里定义属性并赋值 直接定义在window上了,this指向也是window了;

3、在另一个对象的作用域中调用:使用call强制替换this的指向,比如现在有新新对象var  a=new Object();a对象里没有添加任何成员,现在要在新的对象a的作用域中调用上一段中我们创建的Student构造函数(类),具体操作如下:

Student.call(a,'大明','16','M','1');

此时就可以在对象a中访问name、age、gender、class等属性了;

alert(a.name); //大明

以上我们讲解了2种创建构造函数(类)和3种通过构造函数(类)创建实例对象的方法;那么构造函数(模仿的类)在直接使用中有什么弊端吗?来通过下面这短代码观察一下构造函数(模仿的类)在直接使用过程中有什么弊端。


function Student(name,age,gender,class){
     this.name=name;
     this.age=age;
     this.gender=gender;
     this.class=class;
     this.intro=function(){
               alert("我的名字叫"+this.name+"我来自"+class+"班");
            }
 }

var  s1=new Student('小红',12,'FM','4');//创建了一个对象  小红
var  s2=new Student('小明',13,'M','4'); //创建了一个对象  小明
 
alert(s1.name==s2.name);     //false 
alert(s1.intro==s2.intro);   //false

通过上述代码,我们可以看到将方法和属性都定义在构造函数中,每创建一个新的对象时,都会为每个对象创建相同方法和属性的副本,这样就会有大量重复相同的代码出现,浪费内存影响代码的执行效率,我们每创建一个实例对象执行的属性和方法是相同的所以没有必要每次都去创建一个构造函数中想同方法和属性的副本。到这里有一些聪明的人就发现了,他把所有的属性和功能方法都放在构造函数外,在构造函数里以引用的方式去使用这些属性和方法,这些属性和方法就被公共出来了,那么问题又来了全局变量容易被污染或者篡改不安全还一直占用内存!显然这样不合适,那么有一种方法能够让这些共同的代码被共用并且还不是全局的办法吗?有的,在函数中是有的,那就是函数的prototype属性;

封装的问题:将方法和属性定义在构造函数中,通过这个构造函数创建的所有的实例对象多会为他们创建相同的属性和方法的副本,浪费内存;【解决:使用继承,JS中的继承都是通过原型对象实现的】

所以我们引出了下一个话题就是:原型对象和继承;

 4、原型概念

我想之前有很多人知道原型这个概念,但是不清晰怎么去使用原型和只知道原型这样一个名词的大有人在;原型是通过一个函数的prototype属性指向的一个空对象——原型对象;那么原型对象在面向对象编程中究竟扮演着一种什么样的角色,原型对象、构造函数和实例对象有着怎样的三角关系?且听郭某给大家慢慢说来;我也希望大家能通过我的这一段文字和代码对于原型对象有一个更加深入的理解;

原型=>prototype,原型是关于继承的,放在原型对象里的属性和方法是可以被由本构造器下所有实例对象所共享的,所以原型对象也可以理解成是集中存储同一类型所有子对象共有成员的的父对象;创建函数的时候都有一个prototype属性,这个属性其实就是一个指针,总是指向一个对象,这个对象的用途就是将你特定的属性和方法包含在内让所有实例对象共享的作用!所以这样就解决了上述通过构造函数法每创建一个新对象就重新复制一套属性和方法,这样放在同一个构造函数的prototype指向的对象里的所有的属性和方法,可以被所有使用本构造函数创建出来的实例对象共享;


//创建一个构造函数Student,保存的学生是同一个班级相同性别具有相同功能的*;
function Student(name,age){//name和age不同每次都需要重新创建
      this.name=name;
      this.age=age;
}
//每个函数都有一个prototype属性,这个属性总指向一个对象;
var obj=Student.prototype;//这里的obj 就是名为Student函数的prototype属性指向的函数
//把所有相同的属性和方法共享出来;
obj.gender='男';
obj.class=4;
obj.intro=function(){
       alert("我的名字叫"+name+"我来自"+class+"班");
}
 
var  s1=new Student('小红',12);//创建了一个对象  小红
var  s2=new Student('小明',13); //创建了一个对象  小明

alert(s1.gender==s2.gender);     //true
alert(s1.intro==s2.intro);       //true

/*放在构造函数.prototype属性里的属性和方法可以共享不需要每一次重新创建,提高代码执行的效率*/

所以就上面的代码我们可以看出来,就是new出来一百万个对象放在函数prototype属性指向的对象我们JavaScript称其为原型对象里的共同属性和方法只有一个,也就是放在原型对象里的所有方法和属性被new出来的一百万个实例对象共享了;我的天呐,这得节约多少代码啊~~~~~~所以这样一来就把直接调用构造函数创建对象的弊端给解决掉了。下面我用一张图具体来标识一下构造函数、原型对象和实例对象之间的关系;

所以构造函数、原型对象 、实例对象之间就有如下表达式:

构造函数.prototype = 原型对象
原型对象.constructor = 构造函数
实例对象.prototype = 原型对象

5、原型对象API:

.isPrototypeOf(Object): 用于判断传入的对象是否是另外一个对象的原型。=>alert(obj.isPrototypeOf(s1));表述的意思就是s1的原型对象是obj吗?

.hasOwnProperty();用于判断一个对象属性是自有属性吗? 可用此方法判断属性是属于自己的还是继承自原型对象的;=>obj.hasOwnProperty('name');obj里有name属性吗?有返回true,没有返回false;

.in   用于判断属性是否存在于 实例对象原型对象中;不管是存在于实例对象中还是原型对象中只要当前对象能访问到就返回true;'name' in s1;=>只要在s1中或者s1的原型对象中任一个有就返回true;

Object.getPrototypeOf();  由实例对象获得原型对象;var prototypeObj = Object.getPrototypeOf(s1);=>获取实例对象s1的原型对象,(本例中获取完成以后用一个变量接收了,也可以使用链式操作直接使用);

Object.keys();  获取到当前对象里的所有keys(属性名) 返回一个数组;

Object.getOwnPropertyNames(); 遍历对象所有的属性(不论该内部属性能否被遍历),返回一个包含所有遍历到属性的一个数组;var attributes = Object.getOwnPropertyNames(Student.prototype);

6、原型对象的具体使用:

原型对象在JS中有俩大主要的作用:第一就是实现继承,另外一个作用就是扩展对象中的属性和方法:

下面通过实现一个自定义遍历多维数组的方法来体会原型对象是怎么去给对象扩展属性和方法的;我们知道ECS5针对数组API提出了一个遍历数组的新方法就是arr.forEach(function(val,i,arr){//val:值;i:下标;arr:数组变量名}),这个方法可以对数组内每个元素执行相同的操作,但是它值能适用与一维数组,多维数组遍历它会把大于一维数组的数组元素作为一个整体拿出来;那么下面通过向Array原型对象里扩展一个自定义的可以遍历多维数组的each方法;


// 向Array里添加 each方法
// ECMA5 forEach 循环遍历数组的每一项(只适用于遍历一维数组)
/*
var arr = [1,2,3,[a,b,c]4,5];
arr.forEach(function(val , i , arr){
    alert(val);
});  //查看弹出结果的不同
*/
// 自己向Array里添加一个 each方法 能遍历多维数组 
var arr = [1,2,3,[4,[5,[6]]]]; // arr.length
                    
Array.prototype.each = function(fn){
    try{
        //1 遍历数组的每一项x需要一个计数器记录当前遍历的元素位置
        this.i || (this.i=0);  //使用自定义属性和短路逻辑检测并初始化一个计数器=>var i = 0 ;
        //2 判断什么时候去走each核心方法
        // 当数组的长度大于0的时候 && 传递的参数必须为函数
        if(this.length >0 && fn.constructor == Function){
            // 循环遍历数组的每一项
            while(this.i < this.length){    //while循环的范围 
                //获取数组的每一项
                var t = this[this.i];
                //如果获取到当前元素是一个数组
                if(t && t.constructor == Array){
                    // 直接做递归操作
                    t.each(fn);
                } else {
                    //如果不是数组是一个单个元素
                    // 这的目的就是为了把数组的当前元素传递给fn函数 并让函数执行
                    //fn.apply(t,[t]);
                    fn.call(t,t);
                }
                this.i++ ;
             }
             his.i = null ; // 释放内存 垃圾回收机制回收变量
         }
     } catch(err){
           // 错误提醒
     }
return this ;
}
                    
arr.each(function(val){
   alert(val);
46});

简单原型使用: 

之前我们已经学过了原型如何使用,那么现在我们介绍一种简单原型的使用方式:即直接通过对象直接量来重写整个原型对象(这种方法会改变原型对象的构造器);按照惯常的方法就是我们创建一个构造函数,然后通过构造函数的prototype的属性来为原型对象添加属性和方法,每次只能添加一个属性或者方法,操作起来有一些繁琐,那么简单原型只用就是把一个空对象或者是已经封装好要添加给原型对象的一些属性和方法的匿名对象赋值给这个构造函数的原型对象上,注意在这里执行的是赋值操作很显然这个匿名的对象的父类是一个Object,所以就有:构造函数类型名.prototype.constructor=>function Object(){[native code]} ; 所以简单原型使用方法就改变了构造函数的原型对象的构造器;那么怎么去解决这个问题呢?我们可以在简单原型方法封装匿名对象时首先添加一个属性constructor并赋值为构造函数的名称(constructor:构造函数的类型名);我们知道在对象中构造器(constructor)属性是不可以被遍历到的,如果这样我们手动添加了constructor属性原型对象的构造器是保持一致了但是【问题来了】在这里的constructor属性是可以被遍历到的;【解决】ES5中的Object.defineProperty();方法可以为原型对象重新加入并设置构造器属性。(注意:因为是ES5的新特性所以本方法对于火狐4和IE8以前的浏览器是不被支持的;);本方法有3个参数分别是:参数1 :重设构造器的对象 ;参数2:设置什么属性 ; 参数3:options配置项(通常是一个对象);

function Student(){
                
}
            
Student.prototype = {
    //constructor : Student ,    //重新校正原型对象的构造器,此情此景下的constructor属性是可以被遍历到的;
    name: &#39;小明&#39; , 
    age : 12 , 
    gender : &#39;男&#39; ,
    intro : function(){
        alert(&#39;我是小明,我喜欢小红!&#39;);
    }
};
            
// ECMAScript5 提供给原型对象重新设置构造器的方法 Object.defineProperty();
// 3个参数  参数1 :重设构造器的对象   参数2:设置什么属性  参数3:options配置项
/*Object.defineProperty(Student.prototype , &#39;constructor&#39; , {
                 enumerable : false , //设置属性不可被遍历
                 value : Person 
            });    */        
            
            var s1 = new Student();
            //alert(s1.name);
            //s1.intro();
            alert(Student.prototype.constructor); // Student
            
            // 遍历对象的keys,使用第一种方法constructor是可以被遍历到的,使用ES5特性添加是不可以被遍历到的;
            for( attr in s1){
                alert(attr);
            }

原型的动态性(注意原型和创建实例的先后顺序)——>涉及到一个原型链的概念;

原型链:由多级父元素连续继承形成的链式结构,保存着所有的属性和方法,控制着成员的使用顺序,沿原型链自底向上依次查找使用;

原型链    VS    作用域链    

作用域链:保存着所有变量,控制着变量的使用顺序,沿作用域链自上向下查找使用;

我们知道JavaScript本身有一个特性就是解释执行即从上到下一行一行解析执行操作;我们还知道在JavaScript的对象中实例对象和原型对象是有继承关系的;所以就有原型对象的定义必须要先于实例对象的创建而创建,否则就不能使用到原型对象里的方法和属性了;所以简单原型的使用顺序必须是先定义远行对象然后再new实例对象!

PS:原型对象的创建一定是先于实例对象的创建的;

function Student(){

}
var s1 = new Student();  // 此时实例s1的原型对象就是一个空对象=>{}
//----------------------------------------------------------------------    
//在这里使用简单原型定义原型属性和方法实际上他的构造器属性已经发生了改变,构造器变成了Object(实质上就切断了Student和Student.prototype的关联关系了);
//我们为了一致又自己手动添加了一个constructor属性和其他需要的属性,所以在此之前实例的s1的原型对象是没有这些属性;        
Student.prototype = {
    constructor : Student ,    //校正原型对象的构造器
    intro: function(){
             alert(&#39;我是小明,我喜欢小红!&#39;);
    }
};    
            
//s1.intro(); // error 因为原型对象里面没有任何属性和方法
// 注意简单原型使用的顺序(实例对象必须在原型对象之后创建)
var s2 = new Student();
s2.intro();//我是小明,我喜欢小红!

原型对象中常用的开发模式:

代码中介绍三种常用方式:

// 混合使用原型和构造函数式 (模拟定义一个类即构造函数 开发时最常用的方式)
            /*
            function Person(name , age , friends , job){
                this.name = name ; 
                this.age  = age ; 
                this.friends = friends ;
                this.job = job ;
            }
            
            Person.prototype = {
                constructor: Person , 
                sayName : function(){
                    alert(this.name);
                }
            };
            
            var p1 = new Person(&#39;z3&#39; , 20 , [&#39;王五&#39;,&#39;赵六&#39;] , &#39;技术总监&#39;);
            var p2 = new Person(&#39;李四&#39;, 25 , [&#39;王五&#39;,&#39;赵六&#39; ,&#39;赵7&#39;] ,&#39;boss&#39;);
            
            alert(p1.friends);
            p1.sayName();
            alert(p2.friends);
            p2.sayName();
            */
            
//动态原型模式:(所有的代码 都封装到一起)
            /*
            function Person(name , age , friends , job){
                this.name = name ; 
                this.age  = age ; 
                this.friends = friends ;
                this.job = job ;
                
                //动态原型方法:
                if( typeof this.sayName != &#39;function&#39;){
                    Person.prototype.sayName = function(){
                        alert(this.name);
                    }
                }
            }            
            */
            
//稳妥构造函数式: durable object(稳妥对象) 非常安全的环境中
            //特点: 1 、没有公共属性 , 2 、不能使用this对象
            /*
            function Person(name , age , job){
                    // 创建一个要返回的对象
                    var obj = new Object();
                    //可以定义一下私有的变量和函数 private
                    var name = name ; 
                    //var sex = &#39;男&#39;;
                    //var saySex = function(){};
                    //添加一个方法
                    obj.sayName = function(){
                        alert(name);
                    }
                    return obj ;                
            }
            var p1 = new Person(&#39;张三&#39;);
            p1.sayName();
            */

7、深入理解使用原型对象实现继承:

1、使用prototype实现继承——原型继承

参数要传递给被继承原型对象的父类实例中;

function A(){...}

A.prototype = {
   a : "a",
   sayA : function(){
       console.log("this is a.");
   }
}
// 1 创建A()构造器的对象
var a = new A();

function B(){
    this.b = "b";
    this.sayB = function(){
           console.log("this is b.");
    }
}
// 1 将B.prototype指向A()构造器创建的对象
//B.prototype = a;//显然此时B的原型对象将包含一个指向A原型的指针,相应的A原型中也包含着一个指向A构造函数的指针。


// 2. 将B.prototype指向A.prototype
B.prototype = A.prototype;

var b = new B();
console.log(b.b);
b.sayB();

console.log(b.a);
b.sayA();

2、类继承(借用构造函数继承)有时候也管他叫call和apply继承——只继承构造函数不继承原型对象;

function A(){
        this.a = "a";
        this.sayA = function(){
            console.log("this is a.");
        }
    }

function B(){
        // 调用A.call(this)方法,实现继承
        A.call(this);
        
        this.b = "b";
        this.sayB = function(){
            console.log("this is b.");
        }
    }

var b = new B();
console.log(b.b);
b.sayB();

console.log(b.a);
b.sayA();

3、原型继承+借用构造函数继承 = 混合继承// 父类

function Person(name, age){
    this.name = name ; 
    this.age  = age ;
}
// 父类的原型对象属性
Person.prototype.id = 10 ;
Person.prototype.sayName = function(){alert(this.name);};
// 子类
function Boy(name , age , sex){
    // call apply
    Person.call(this,name,age); // 1 借用构造函数继承 继承父类的模版
    this.sex = sex ; 
}    
// 2 原型继承
// 只剩下 父类的实例 和 父类的原型对象的关系了
Boy.prototype = new Person(); //继承父类的原型对象
var b = new Boy(&#39;李四&#39; , 20 , &#39;男&#39;);
alert(b.name);
alert(b.sex);
b.sayName();

在混合继承中我们知道在原型继承的这一步只是没有传递参数,没有执行构造函数罢了,其实同样是继承了构造函数和原型对象,那么在混合继承中就做了三件事:继承了俩遍构造函数和继承了一遍原型对象,【问题来了】既然构造函数已经被继承过一遍了能不能再只继承一遍原型对象呢?【解决】自定义一个方法只继承原型对象;

function extend(child ,father){
    // 目的: 实现只继承父类的原型对象
    var F = new Function();    // 1 创建一个空函数    目的:空函数进行中转
     F.prototype = father.prototype; // 2 实现空函数的原型对象和超类的原型对象转换
    child.prototype = new F();     // 3 原型继承 
    child.prototype.constructor = child ; // 还原子类的构造器
    //保存一下父类的原型对象: 一方面方便解耦  另一方面方便获得父类的原型对象
    child.superClass = father.prototype; //自定义一个子类的静态属性 接受父类的原型对象
    //判断父类的原型对象的构造器 (加保险)
     if(father.prototype.constructor == Object.prototype.constructor){
        father.prototype.constructor = father ; //手动欢迎父类原型对象的构造器
    }
}
            
            
// 混合继承:原型继承和借用构造函数继承
function Person( name , age){
    this.name = name ; 
    this.age = age ; 
}
            
Person.prototype = {
    constructor: Person ,
    sayHello: function(){
            alert(&#39;hello world!&#39;);
    }
};
            
function Boy(name , age , sex){
    //call 绑定父类的模版函数 实现 借用构造函数继承 只复制了父类的模版
    Boy.superClass.constructor.call(this , name , age);
        this.sex = sex ;
}
            
//原型继承的方式: 即继承了父类的模版 又继承了父类的原型对象
//Boy.prototype = new Person();
// 只继承一遍父类的原型对象
extend(Boy , Person);
            
// 给子类加了一个 原型对象的方法
Boy.prototype.sayHello = function(){
    alert(&#39;hi javascript!&#39;);
}
            
var b = new Boy(&#39;张三&#39; , 20 , &#39;男&#39;);
alert(b.name); 
alert(b.sex);
//b.sayHello();
Boy.superClass.sayHello.call(b);
            
//alert(Boy.superClass.constructor);
// 混合继承的缺点: 3件事 : 继承了父类的2次模版 , 继承了一次父类的原型对象
// extend方法 2件事: 继承1次父类的模版 继承一次父类的原型对象

 8、多态:override—重写

在上一个自定义只继承原型的例子里,代码41到43行就是一个重写,自定义属性superClass就是保存了父类的原型对象确保在重写之后还能很便捷的使用到父类的原型对象成员;

以上是JavaScript中的面向对象的程序设计简介的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn