WEB前端第三十八课——js类和对象,继承,设计模式

  • A+
所属分类:Web前端
摘要

1.基础  类,是一种抽象的,并不实际存在的,表示一种事物共有特征的描述

1.基础

  类,是一种抽象的,并不实际存在的,表示一种事物共有特征的描述

  对象,是具体的,实际存在的,类中的某一个体

  JavaScript是一个典型的“面向过程思想”的语言,语言中不存在类和对象的概念,

  但是,由于JavaScript经常要解决“面向对象思想”的问题,所以JavaScript使用一些方法模拟面向对象思想的特征。

 2.类的创建

  构造函数,通过 new命令生成对象的函数称为构造函数,构造函数一般首字母大写

  new命令的作用是先创建一个对象,然后让对象调用构造函数,因此,构造函数中的 this指向的是 new创建的对象

  语法示例:

    function Student(name, age, sex){

      this .name=name;

      this .age=age;

      this .sex=sex;

    };

    var Lili = new Student('Lili', 20, 'female');

  上述代码中,函数 Student代表类名,通过传递实参向类的属性赋值,new命令将这些键值对构造为对象,然后赋值给变量

  注意,这种方法只是 js中众多创建类的方式之一,且并不是最优的创建方式

3.面向对象思想

  面向对象(oop),创建一个对象,让对象拥有做某件事的能力(给对象属性和方法),然后命令对象做某件事(封装、继承、多态)

  面向过程(pop),分析出解决问题的所有步骤,将其构建为一个一个函数,然后将这些步骤按照一定顺序实现(顺序、选择、循环)

  oop的核心就是如何构建一个对象,也就是“对象的封装”!

4.封装

  是指构造具有某种特征的类,以通过对其进行实例化,来获得满足需求的对象的过程

  封装的特征:

    公有,对象中的属性和方法,在对象外部能够直接访问的,称为公有属性和方法

    私有,对象中的属性和方法,仅在对象内部才可以使用的,称为私有属性和方法

  在构造函数中,通过“this .属性”的方式为类添加公有的属性和方法

    this.property所添加的内容在对象外部能够直接被访问

  在构造函数中,添加局部变量和闭包的方式为类添加私有的属性和方法

    局部变量保证了对象外部无法直接获取,闭包保证了对象外部可以间接获取

  对于所有对象都具有的共同属性,为了减少在构造对象时重复传递相同属性值,通过 prototype属性进行统一定义

    语法:类名 .prototype .相同属性值的属性 = 属性值;

5.原型

  js中给函数提供了一个对象类型的属性,叫作 prototype(原型)

  原型归函数所有,不用创建,是默认就存在的

    语法:Class .prototype .property = ' value ';

  注意,js中提供了一种机制,如果是通过类创建的对象,当访问的属性在对象中没有找到时,

     会去找创建这个对象的类的原型属性,如果能找到,则视为当前对象拥有这个属性。

  本质上,原型的存在就是为了给类的对象添加共同的属性

  作用,使用原型能够有效地节约内存空间,通过原型创建的属性和方法,能够被所有这个类创建的对象访问

6.构造函数语法小结

  function  ClassName(para1,para2,...) {    //类名首字母须大写
   var privateProperty = 'value';      //定义私有属性
   var privateFunc = function () { }      //定义私有方法
   this.publicProperty1 = para1;      //定义公有属性
   this.publicProperty2 = para2;
    
this.publicFunc = function(){ };      //定义公有方法(特权函数,可以读写privateProperty)
      ... ...
  }
  ClassName.prototype.publicProperty3 = 'value3';  //定义原型(共有)属性及属性值
  ClassName.prototype.publicFunc=function () { };   //定义原型(共有)方法

   var  obj = new  ClassName(实参1,实参2,…);    //通过类创建对象

7.原型属性

  结构:原型是一个对象,在原型中通常有两个属性

    ① 构造器Constructor,该属性指向了这个类本身(表明当前原型归属于哪个类所有)

    ② 原型指向_proto_,该属性指向原型本身,提供给通过类创建的对象使用

  作用:创建类的公有属性和公有方法,为创建对象服务

     节约内存空间,不必为每一个对象都分配公有属性和公有方法的内存

  缺点:原型中不能保存数组这类引用类型的数据

     因为地址传递的问题会导致出现修改的连锁变化

     比如,通过 obj .publicProperty .pop(); 删除共有属性数组中的元素,

        会导致其他对象访问 prototype .publicProperty 数组时也缺少了这个元素

8.原型链

  原型链构成,由对象的“_proto_”属性和对象的构造函数的原型的“_proto_”属性构成的链式结构称为原型链

        原型链的顶端是 Object对象,Object对象没有“_proto_”属性,或者说它的“_proto_”属性指向了自身

  原型链作用,访问对象的属性或方法时,首先在对象本身中查找是否拥有这个属性或方法,

        如果没有找到,那么就沿着原型链逐级向上查找,直到 Object为止

        在任何一级寻找到这个属性或方法都视为对象拥有这个属性或方法(继承)

  原型链创建,函数的原型(prototype) 设置为 另一个函数的对象(实例)

        语法示例:ClassName101 .prototype = new  ClassName1 ;

9.继承

  在面向对象的语言中,子类能够在不声明的情况下,使用父类的属性和方法的特性叫作继承

  而JavaScript语言本质上并不是一门面向对象的语言,所以,需要通过某种手段来模拟继承,这个方法就是“原型链”

  链式继承示例:

<script>     function RichMan() {}     RichMan.prototype.money='billions of pounds';     var father=new RichMan();   //创建父类的实例化对象      function Son() {}     Son.prototype=father;       //创建子类继承关系          var boy=new Son();          //创建子类实例化对象     console.log(boy.money);     //billions of pounds </script> 

  存在的问题:

    ① 原型链继承,子类实例化不能向父类构造函数传参,但是可以直接访问父类的原型属性

     构造继承,只能访问到父类实例属性,实例化时可以向父类构造函数传参,但是不能访问父类的原型属性和方法

    ② 原型链继承,子类 prototype的 constructor属性,实际上就是父类 prototype的 constructor属性,这样并不合理

     构造函数,子类 prototype的 constructor属性是子类本身,父类也是同样,但构造继承的子类无法享有父类的prototype属性和方法

  构造继承示例:

    function RichMan(fcash,fhouse,fcar) {         this.cash=fcash;         this.house=fhouse;         this.car=fcar;     } /*    RichMan.prototype.money='billions of pounds';     var father=new RichMan();   //创建父类的实例化对象*/      function Son(scash,shouse,scar) {         RichMan.call(this,scash,shouse,scar);      //创建子类构造继承关系     }     var john=new Son(1,2,3);        //可以访问父类实例属性,but not 原型属性     var dancy=new Son(4,5,6); 

10.组合继承

  为了解决“原型链继承”和“构造继承”的各自缺点,在创建子类继承时同时使用两种继承方式,即组合继承

 1 <script>  2     function RichMan(fcash,fhouse,fcar) {  3         this.cash=fcash;  4         this.house=fhouse;  5         this.car=fcar;  6     }  7     RichMan.prototype.money='billions of pounds';  8     // var father=new RichMan();   //创建父类的实例化对象  9  10     function Son(scash,shouse,scar) { 11         RichMan.call(this,scash,shouse,scar);   //创建“构造继承”关系 12     } 13     Son.prototype=new RichMan();        //创建“链式继承”关系 14     var john=new Son(1,2,3); 15     var dancy=new Son(4,5,6); 16     console.log(john);                 //Son {cash: 1, house: 2, car: 3} 17     console.log(dancy.money);          //billions of pounds 18  19 /*    Son.prototype=father;       //创建子类继承关系 20     var boy=new Son();          //创建子类实例化对象 21     console.log(boy.money);     //billions of pounds*/ 22 </script>

  组合继承的弊端:

    在子类中调用了两次父类构造函数,一次用于构造继承,一次用于链式继承,也就是对实例属性初始化了两次,

    但这一弊端不太致命,子类在实例化时,构造继承的实例属性覆盖了链式继承的实例属性,只是多消耗了一些内存

  寄生组合继承

    核心思想:通过寄生方式,砍掉父类的实例属性,这样就能在调用两次父类的构造的时候,不会再次实例属性/方法。 

 1 <html lang="en">  2 <head>  3     <meta charset="UTF-8">  4     <title>继承</title>  5 </head>  6 <body>  7   8 <script>  9     function RichMan(fcash,fhouse,fcar) { 10         this.cash=fcash; 11         this.house=fhouse; 12         this.car=fcar; 13     } 14     RichMan.prototype.money='billions of pounds'; 15     // var father=new RichMan();   //创建父类的实例化对象 16  17     function Son(scash,shouse,scar) { 18         RichMan.call(this,scash,shouse,scar);   //创建子类“构造继承”关系 19     } 20     // Son.prototype=new RichMan();       //创建“链式继承”关系 21     Son.prototype.constructor=Son;        //将子类原型属性‘constructor’指向子类本身! 22  23     (function () {                     //创建自执行函数,并嵌套一个空的构造函数 Medi, 24         function Medi() { }             //将空构造函数插入到原型链作为中间节点,即原子类变为孙类 25         Medi.prototype=new RichMan();   //这样就避免在每一次子(孙)类实例化时进行两次父类实例化属性和方法 26         Son.prototype=new Medi();       //也就是用这种寄生方式替代原来的直接“链式继承”关系 27     }()); 28  29     var john=new Son(1,2,3); 30     var dancy=new Son(4,5,6); 31     console.log(john);                 //Son {cash: 1, house: 2, car: 3} 32     console.log(dancy.money);          //billions of pounds 33  34 /*    Son.prototype=father;       //创建子类继承关系 35     var boy=new Son();          //创建子类实例化对象 36     console.log(boy.money);     //billions of pounds*/ 37 </script> 38 </body> 39 </html>

 

11.设计模式

  设计模式(Design Pattern)是一套被反复使用的、多数人知晓的、经过分类的代码设计经验的总结(模板)

  作用:提高代码可重用性、让代码更容易被他人理解、提高代码的可靠性,

     设计模式使代码编写真正工程化,是软件工程的基石脉络,如同大厦的结构一样

  常见种类:

    ① 工厂模式

    ② 构造函数模式

    ③ 原型模式

    ④ 混合模式

    ⑤ 动态原型模式

12.工厂模式

    <script>         function RichMan(fcash,fhouse,fcar) {             var richMan={};             //定义局部变量,类型为Object对象             richMan.cash=fcash;         //直接定义内部对象的属性             richMan.house=fhouse;             richMan.car=fcar;             return richMan;             //将内部对象作为调用函数的返回值         }         var man=RichMan('muchCash','bigHouse','luxuryCar');         console.log(man);         console.log(man instanceof RichMan);        //返回值为 false     </script> 

  工厂模式是软件开发中经常被使用的一种设计模式

  instanceof 方法一个实例是否归属于一个类。

  通过工厂模式创建的对象,最大的问题是无法确定其属于哪一个类!

13.构造函数模式

    <script>         function RichMan(fcash,fhouse,fcar) {             this.cash=fcash;         //定义this属性             this.house=fhouse;             this.car=fcar;         }         //通过 new命令创建对象         var man=new RichMan('muchCash','bigHouse','luxuryCar');         console.log(man);         console.log(man instanceof RichMan);        //返回值为 true     </script> 

  构造函数和工厂模式最大的区别是:

    没有显示创建一个对象,而是通过 new命令隐式创建一个对象

    然后让隐式对象来实际执行构造函数,因此构造函数中的 this指向的是这个隐式对象

  通过构造函数创建的对象可以明确判断其归属于哪一个类

  构造函数创建对象必须使用 new命令,函数名的首字母通常大写

  构造函数模式最大的问题是面对子类对象共有的属性值,不能有效地节约内存占用!

14.原型模式

    <script>         function RichMan(fcash,fhouse,fcar) {}         //使用 prototype方法定义类的共有属性         RichMan.prototype.cash='muchCash';         RichMan.prototype.house='bigHouse';         RichMan.prototype.car='luxuryCar';         //通过 new命令创建对象         var man=new RichMan();         man.house='manyHouse';  //对于相同属性不同属性值时单独赋值         console.log(man);         console.log(man instanceof RichMan);        //返回值为 true     </script> 

  弊端:在处理不同属性值的公有属性时,增加了内存的空间占用!

15.混合模式

    <script>         function RichMan(fcash,fhouse,fcar) {             this.cash=fcash;              this.house=fhouse;             this.car=fcar;         }         RichMan.prototype.advantage=function () {             console.log('数钱数到手抽筋')         }         //通过 new命令创建对象         var man=new RichMan('muchCash','bigHouse','luxuryCar');         console.log(man);         man.advantage();     </script> 

16.动态原型模式

    <script>         function RichMan(fcash,fhouse,fcar) {             this.cash=fcash;             this.house=fhouse;             this.car=fcar;             //使用“懒加载”的方式,定义共有属性的原型             if (typeof RichMan._initialized=='undefined'){                 RichMan.prototype.advantage=function () {                     console.log('数钱数到手抽筋')                 }                 RichMan._initialized=true;             }         }         //通过 new命令创建对象         var man=new RichMan('muchCash','bigHouse','luxuryCar');         console.log(man);         man.advantage();     </script> 

  懒加载:使用时才加载和占用内存空间,在没有使用之前相当于不存在

  动态原型模式和混合模式很相似,二者都是为了解决原型模式中所有内容都公有的问题

  动态原型模式的特点在于,

    通过判断一个类的 “._initialized” 属性的类型(typeof),进而判断这个类有没有被实例化过,

    如果没有被实例化过,在第一次调用(初始化)时就会将其释放,并将属性值写为 true。

  “._initialized”属性是每一个类都拥有的私有属性,它仅用来表示类是否被实例化过,是Boolean类型的可读写属性。