js -- 理解面向对象(创建对象与继承)

目录

一、创建对象

1. 工厂函数模式

2. 构造函数模式

1. 构造函数与函数区别

2. 构造函数的问题

3. 原型模式

1.原型写法

2.原型对象

3.原型对象的改嫁现象

4.原型对象的问题

4. 构造函数+原型模式

5. 动态原型模式

二、继承

1. 原型链

1.实现原型链

2.子类方法的定义与重写

3.原型链问题

2. 借用构造函数(伪继承)

3. 组合继承(伪经典继承)

4. 寄生组合式继承


声明: 博客中关于js篇都是在node环境下测试,若在浏览器中有些地方结果可能有所不同但原理相同。

一、创建对象

1. 工厂函数模式

 该模式抽象了创建具体对象的过程,解决了创建多个相似对象的问题。


            存在问题:无法区分对象类型
         

          function createPerson(name){
                   var o={
                       name:name,
                       sayName:function(){
                           console.log(this.name);
                       }
                   }
                   return o;
           }
           var person=createPerson('cc');
           var dog=createPerson('dog');
           console.log(person.contructor);  //[Function:Object]
           console.log(person.constructor==dog.constructor)  //true


          ***: person对象和dog对象都属于Object构造函数所创造。

2. 构造函数模式

 使用该函数模式可以解决了对象类型无法区分问题。


               我们现在重写上面的工厂函数中的例子:
             

               function Person(name){
                   this.name=name;
                     //这样会重复创建匿名函数
                   this.sayName=function(){
                       console.log(this.name);
                   };
               }
               var person1=new Person('cc');
               var person2=new Person('wgs');
               console.log(person1.constructor); //[Function:Person]
               console.log(person1.constructor==person2.constructor); //true


               上面的代码调用构造函数经历了4个步骤:
                   1. 创建一个新对象
                   2. 将构造函数的作用域(原型的地址)赋给新对象(因此this 指向了这个新对象)
                   3. 执行构造函数中代码
                   4. 返回新对象


               对于上面的代码 person1和person2它们虽然是Person创造的,但同时也是Object的实例

                   console.log(person1 instanceof Object);  //true
                   console.log(person2 instanceof Object);  //true


      其实也不用这么证明,细心的同学可能会注意到,constructor属性其实是Object构造方法所持有的。换句话说所有对象均继承自Object。

 

1. 构造函数与函数区别


                 1. 构造函数是通过new 操作符来调用,普通函数则不需要
                 2. 构造函数首字母大写,而普通函数小写

                 当作构造函数使用

                     var person=new Person('cc');
                     person.sayName(); //"cc"


                 当作普通函数使用

                 Person("cc");  //node 环境下 添加到global中 浏览器环境中 添加到window中
                 Global.sayName(); //"cc"
                 //在一个对象的作用域中调用
                 var o=new Object();
                 Person.call(o,'wgs');
                 o.sayName(); //"wgs"


                 **:也可以使用 apply() 关于两者区别请看 js引用类型-Function类型中的函数的属性和方法

2. 构造函数的问题


      每个方法都要在每个实例中重复创建一遍。person1和person2都有一个名为sayName()的方法,但两个方法不是用一个Function实例。
             

         console.log(person1.sayName==person2.sayName); //false


                  因此我们可能会想到这样解决:

                      function Person(name){
                          this.name=name;
                          this.sayName=sayName;
                      }
                      function sayName(){
                          console.log(this.name);
                      }
                      var person1=new Person('cc');
                      var person2=new Person('wgs');
                      console.log(person1.sayName==person2.sayName) //true


                 **:这样确实解决了同一个函数重复声明的问题,那么这样做让我们体现不到封装性。即sayName()不只是Person构造函数拥有,好在原型模式可以解决这个问题。

3. 原型模式

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象(原型对象)。这个对象的好处是可以让所有对象实例共享它所包含的属性和方法。


1.原型写法

                 function Person(){}
                 Person.prototype.name="cc";
                 Person.prototype.sayName=function(){
                     console.log(this.name);
                 }
                 var person=Person();
                 person.sayName()  //"cc"


2.原型对象


                 每创建一个函数,该函数会自动创建一个prototype属性,这个属性指向原型对象。而所有的原型对象会有一个constructor(构造函数),该属性指向prototype属性所在函数的指针。
                 下面证实我所说的:

                     function Person(){}
                     console.log(Person.prototype.constructor==Person)  //true


                 当new一个对象后,该对象就会有一个_proto_属性,该属性指向构造函数的原型对象。换句话说 该属性连接实例与构造函数的原型对象,而不是连接实例与构造函数
                 实例属性和原型属性
                      当实例中的属性和原型中的属性重名时,会自动屏蔽原型中的属性

                          function Person(){}
                          Person.prototype.name="cc";
                          var person1=new Person();
                          var person2=new Person();
                          person1.name="wgs";
                          console.log(person1.name); //"wgs"   //来自实例
                          console.log(person2.name);  //"cc"   //来自原型

上面的代码在内存中大致如下图所示:

注明:   图中 proto其实是_proto_ (本人对画图工具不感冒 大家懂我意思就好)


3.原型对象的改嫁现象


                为了从视觉上更好地封装原型的功能,我们会使用字面量来重写整个原型对象如下所示。

                function Person(){}
                Person.prototype={
                    name:"cc",
                    sayName:function(){
                        console.log(this.name);
                    }
                }
                var person=new Person();
                person.sayName();   //"cc"
                console.log(person.constructor)  //[Function Object]


              

上面的改嫁现象用内存图表示如下图:

注明:   图中 proto其实是_proto_ (本人对画图工具不感冒 大家懂我意思就好)
                因此可以做以下更改:

 **:因为使用字面量创建对象,该对象的Constructor属性(指向Object构造函数),不在指向Person函数。

如果此时我们将Object构造出来的实例({})对象加入constructor指向Person。则Person的实例的创建者就指回了Person而不是Object

                    function Person(){}
                    Person.prototype={
                        constructor:Person,
                        name:"cc",
                        sayName:function(){
                            console.log(this.name);
                        }
                    }
                    var person=new Person();
                    console.log(person.constructor)  //[Functon Person]


4.原型对象的问题


                它省略了构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。这个问题我们可以通过隐藏原型对应属性,但是若是引用类型的属性则问题就比较突出,看如下案例:

                    function Person(){

                    }
                    Person.prototype={
                        constructor:Person,
                        name:"cc",
                        friends:["wgs"]
                    }
                    var person1=new Person();
                    var person2=new Person();
                    person1.friends.push("wcc");
                    console.log(person1.friends); //["wgs","wcc"]
                    console.log(person2.friends); //["wgs","wcc"]


             为了解决上述问题,我们组合使用构造函数模式和原型模式。

4. 构造函数+原型模式

    构造函数用于定义实例属性,而原型模式用于定义方法和共享属性。
            下面我们对上面案例中代码改进:
 

                    function Person(name){
                        this.name=name;
                        this.friends=["wgs"];
                    }
                    Person.prototype={
                        constructor:Person,
                           sayName:function(){
                               console.log(this.name);
                           }
                    }
                    var person1=new Person("cc");
                    var person2=new Person("c");
                    person1.friends.push("wcc");
                    console.log(person1.friends); //["wgs","wcc"]
                    console.log(person2.friends); //["wgs"]

5. 动态原型模式

            该模式将所有信息都封装在了构造函数中,而通过构造函数中初始化原型。

            function Person(name){
                this.name=name;
                if(typeof this.sayName!='function'){
                    Person.prototype.sayName=function(){
                        console.log(this.name);
                    }
                }
            } 
             var person=new Person('cc');
             person.sayName(); //"cc"

            **:该模式优点在于只有第一次调用方法时才会创建该方法,以后都不会再创建;而且如果从来都不去调用方法那么该方法就不会被创建。

二、继承

由于函数没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且其实现继承主要依据原型链。

1. 原型链

1.实现原型链

                 function People(name){
                     this.name=name;
                 }
                 People.prototype.sayName=function(){
                     console.log(this.name);
                 }
                 function Man(name){
                     this.name=name;
                 }
                 Man.prototype=new People();
                  var man=new Man('cc');
                  man.sayName();  //"cc"


            **:这样Man就继承了People,但是Object是所有函数的默认原型,因此People继承Object。所有man就可以使用Object的默认方法。


2.子类方法的定义与重写


                    1.当子类要重写父类的方法时,必须要放到父类该方法之后2.必须是在子类的实例替换原型后,在定义或重写方法。
                    3.不能使用对象字面量创建原型方法(因为这样会重写原型链)

                       function SuperType(){

                               this.value=true;
                       }

                       SuperType.prototype.getSuperValue=function(){
                               return this.value;
                       }

                       function SubType(){
                               this.subValue=false;
                       }

                       //继承 SuperType
                       SubType.prototype=new SuperType();

                       //添加方法
                      SubType.prototype.getSubValue=function(){
                               return this.subValue;
                       }
                           //重写超类中方法
                       SubType.prototype.getSuperValue=function(){
                               return false;
                       }

                       var instance=new SubType();
                       console.log(instance.getSuperValue())  //false


              下面使用对象字面量来添加方法

               function SuperType(){

                    this.value=true;
               }
               SuperType.prototype.getSuperValue=function(){
                    return this.value;
               }
               function SubType(){
                   this.subValue=false;
               }
                       //继承 SuperType
               SubType.prototype=new SuperType();

               //使用字面量添加新方法,会导致方上一行代码无效
               SubType.prototype={
                     getSubValue:function(){
                      return this.subValue;
                       }
               }
                       
            var instance=new SubType();
                 //此时SubType不在继承SuperType
            console.log(instance.getSuperValue())  //会报错 getSuperValue is not function     

 
              **:使用字面量后,subType包含了Object的实例,而非SuperType的实例.因此SubType和SuperType之间已经没有关系了。         


3.原型链问题


                    主要问题在于包含引用类型值的原型属性会被所有实例共享。这也是我们为什么要在构造方法中定义属性而不是在原型对象中定义属性的原因。

                    function SuperType(){
                        this.colors=["red","blue"];
                    }
                    function SubType(){}
                    SubType.prototype=new SuperType();
                     var instance1=new SubType();
                      instance1.colors.push('black');
                      console.log(instance1.colors); //["red","blue","black"]
                      var instance2=new SubType();
                      console.log(instance2.colors);//["red","blue","black"]

2. 借用构造函数

    这种方法实现思路:在子类型构造函数的内部调用超类型构造函数

                 function SuperType(){
                     this.colors=["red","blue"];
                 }
                 function SubType(){
                        //借用构造函数
                     SuperType.call(this);
                 }

                 var instance1=new SubType();
                 instance1.colors.push("black");
                 console.log(instance1.colors); //["red","blue","black"]
                 
                 var instance2=new SubType();
                 console.log(instance2);/ /["red","blue"]


             存在问题
                 在父类的原型中定义的方法,对子类型而言是不可见的即(),结果所有类型都只能使用构造函数模式。

3. 组合继承(伪经典继承)

该模式将原型链和借用构造函数的技术组合到一起。
 

                  function SuperType(name){
                      this.name=name;
                      this.colors=["red","blue"];
                  }
                  SuperType.prototype.sayName=function(){
                      console.log(this.name);
                  }
                  function SubType(name,age){
                      SuperType.call(this,name); //第二次调用SuperType
                      this.age=age;
                  }
                      //继承
                      SubType.prototype=new SuperType();  //第一次调用SuperType()
                      SubType.prototype.constructor=SubType;
                      SubType.prototype.sayAge=function(){
                          console.log(this.age);
                      };

                      var instance1=new SubType("cc",12); 
                      instance1.colors.push("black");
                      instance1.sayName();  //"cc"
                      instance1.sayAge(); // 12

                      var instance2=new SubType("wgs",22);
                      instance2.sayName();  //"wgs"
                      instance2.sayAge(); //22
                      console.log(instance2.colors) //["red","blue"] 


             存在问题: 组合继承的最大问题在于无论怎么,都会调用两次超类构造函数;这样会导致创建了两组name 和 colors属性:一组在实例上,一组在SubType原型中,而且实例中的属性会将原型中的两个同名属性屏蔽掉。


             解决方案:  使用寄生组合式继承

4. 寄生组合式继承

             通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。使用寄生式继承来继承超类的原型,然后我们再将结果给指定子类型的原型。
             

             function SuperType(name){
                      this.name=name;
                      this.colors=["red","blue"];
                  }
                  SuperType.prototype.sayName=function(){
                      console.log(this.name);
                  }
                  function SubType(name,age){
                      SuperType.call(this,name); //第二次调用SuperType
                      this.age=age;
                  }
                      
                  function inheritPrototype(subType,superType){
                  var prototype=Object.create(superType.prototype); //创建父类原型的一个副本
                     prototype.constructor=SubType; //弥补重写原型而失去constructor属性
                     subType.prototype=prototype; //让子类原型指向父类原型(副本)
                 }
                 inheritPrototype(SubType,SuperType);
                 SubType.prototype.sayAge=function(){
                          console.log(this.age);
                 };

                      var instance1=new SubType("cc",12); 
                      instance1.colors.push("black");
                      instance1.sayName();  //"cc"
                      instance1.sayAge(); // 12

注明:博客中的知识来自javascript高级程序设计(第三版).若有错误请联系博主。

该书的地址(有空的话还是买书吧):

链接:https://pan.baidu.com/s/13-lbOd67pnnlgAbB9DWpow 密码:82dk 

猜你喜欢

转载自blog.csdn.net/qq_37674616/article/details/82220732